diff --git a/app/src/components/ui/footer/Footer.tsx b/app/src/components/footer/Footer.tsx similarity index 81% rename from app/src/components/ui/footer/Footer.tsx rename to app/src/components/footer/Footer.tsx index 9f73284..1e22ccd 100644 --- a/app/src/components/ui/footer/Footer.tsx +++ b/app/src/components/footer/Footer.tsx @@ -1,11 +1,11 @@ import React from "react"; -import { HelpIcon } from "../../icons/DashboardIcon"; -import { useLogger } from "../log/LoggerContext"; +import { HelpIcon } from "../icons/DashboardIcon"; import { LogInfoIcon, ErrorIcon, WarningIcon, -} from "../../icons/ExportCommonIcons"; // Adjust path as needed +} from "../icons/ExportCommonIcons"; // Adjust path as needed +import { useLogger } from "../ui/log/LoggerContext"; const getLogIcon = (type: string) => { switch (type) { @@ -21,7 +21,7 @@ const getLogIcon = (type: string) => { } }; -const Footer = () => { +const Footer: React.FC = () => { const { logs, setIsLogListVisible } = useLogger(); const lastLog = logs.length > 0 ? logs[logs.length - 1] : null; @@ -43,7 +43,10 @@ const Footer = () => {
-
setIsLogListVisible(true)}> +
setIsLogListVisible(true)} + > {lastLog ? ( <> {getLogIcon(lastLog.type)} diff --git a/app/src/components/icons/analysis.tsx b/app/src/components/icons/analysis.tsx index 30115cc..c387003 100644 --- a/app/src/components/icons/analysis.tsx +++ b/app/src/components/icons/analysis.tsx @@ -12,7 +12,7 @@ export function ThroughputSummaryIcon() { fillRule="evenodd" clipRule="evenodd" d="M13.9063 12.9046L14.2265 13.86L14.4378 14.5073C14.9906 16.2239 15.2594 17.2662 15.2594 17.7299C15.2594 18.8219 14.3742 19.7072 13.2822 19.7072C12.1902 19.7072 11.305 18.8219 11.305 17.7299C11.305 17.2106 11.6422 15.9654 12.3379 13.86L12.658 12.9046C12.8604 12.3082 13.704 12.3082 13.9063 12.9046ZM13.2822 7.84375C16.9222 7.84375 19.873 10.7945 19.873 14.4345C19.873 15.7565 19.4823 17.0219 18.7621 18.0974C18.5596 18.3999 18.1502 18.4809 17.8478 18.2784C17.5453 18.0758 17.4643 17.6665 17.6668 17.364C18.2428 16.5038 18.5548 15.4933 18.5548 14.4345C18.5548 11.5225 16.1942 9.16191 13.2822 9.16191C10.3702 9.16191 8.00956 11.5225 8.00956 14.4345C8.00956 15.4933 8.32153 16.5038 8.89752 17.364C9.10005 17.6665 9.01904 18.0758 8.71659 18.2784C8.41414 18.4809 8.00477 18.3999 7.80224 18.0974C7.08206 17.0219 6.69141 15.7565 6.69141 14.4345C6.69141 10.7945 9.6422 7.84375 13.2822 7.84375ZM13.2822 15.2247L13.0657 15.9238L12.9161 16.4319C12.7219 17.111 12.6231 17.5509 12.6231 17.7299C12.6231 18.0939 12.9182 18.389 13.2822 18.389C13.6462 18.389 13.9413 18.0939 13.9413 17.7299C13.9413 17.511 13.7936 16.9022 13.5044 15.9428L13.2822 15.2247Z" - fill="white" + fill="var(--text-color)" /> ); @@ -33,7 +33,7 @@ export function ProductionCapacityIcon() { fill-rule="evenodd" clip-rule="evenodd" d="M13.5167 7.88281H15.3741C15.668 7.88281 15.9053 8.09496 15.9053 8.35581V13.0575H9.079L9.00279 13.0583V8.35581C9.00279 8.09417 9.24007 7.88281 9.53393 7.88281H11.3921V10.251C11.3921 10.3138 11.4204 10.3735 11.4707 10.4183C11.5226 10.4633 11.5891 10.4879 11.6577 10.4875H13.2511C13.3201 10.4881 13.3868 10.4635 13.4389 10.4183C13.4625 10.3971 13.4815 10.3713 13.4949 10.3426C13.5082 10.3138 13.5157 10.2826 13.5167 10.251V7.88281ZM9.079 14.0389H15.921C16.4243 14.0491 16.9035 14.2561 17.2559 14.6157C17.6082 14.9752 17.8056 15.4585 17.8056 15.9619C17.8056 16.4653 17.6082 16.9486 17.2559 17.3082C16.9035 17.6677 16.4243 17.8748 15.921 17.885H9.079C8.82319 17.8901 8.56892 17.8442 8.33108 17.7499C8.09324 17.6556 7.87661 17.5147 7.69387 17.3357C7.51113 17.1566 7.36596 16.9428 7.26685 16.707C7.16775 16.4711 7.1167 16.2178 7.1167 15.9619C7.1167 15.7061 7.16775 15.4528 7.26685 15.2169C7.36596 14.981 7.51113 14.7673 7.69387 14.5882C7.87661 14.4091 8.09324 14.2683 8.33108 14.174C8.56892 14.0796 8.82319 14.0337 9.079 14.0389ZM10.4626 15.956C10.4626 16.1384 10.3902 16.3132 10.2613 16.4422C10.1323 16.5711 9.95748 16.6435 9.77514 16.6435C9.59281 16.6435 9.41794 16.5711 9.28901 16.4422C9.16008 16.3132 9.08764 16.1384 9.08764 15.956C9.08764 15.7737 9.16008 15.5988 9.28901 15.4699C9.41794 15.341 9.59281 15.2685 9.77514 15.2685C9.95748 15.2685 10.1323 15.341 10.2613 15.4699C10.3902 15.5988 10.4626 15.7737 10.4626 15.956ZM13.1914 15.956C13.1914 16.1384 13.119 16.3132 12.9901 16.4422C12.8611 16.5711 12.6863 16.6435 12.5039 16.6435C12.3216 16.6435 12.1467 16.5711 12.0178 16.4422C11.8889 16.3132 11.8164 16.1384 11.8164 15.956C11.8164 15.7737 11.8889 15.5988 12.0178 15.4699C12.1467 15.341 12.3216 15.2685 12.5039 15.2685C12.6863 15.2685 12.8611 15.341 12.9901 15.4699C13.119 15.5988 13.1914 15.7737 13.1914 15.956ZM15.2319 16.6427C15.4143 16.6427 15.5891 16.5703 15.7181 16.4414C15.847 16.3124 15.9194 16.1376 15.9194 15.9552C15.9194 15.7729 15.847 15.598 15.7181 15.4691C15.5891 15.3402 15.4143 15.2677 15.2319 15.2677C15.0496 15.2677 14.8747 15.3402 14.7458 15.4691C14.6169 15.598 14.5444 15.7729 14.5444 15.9552C14.5444 16.1376 14.6169 16.3124 14.7458 16.4414C14.8747 16.5703 15.0496 16.6427 15.2319 16.6427Z" - fill="white" + fill="var(--text-color)" /> @@ -41,7 +41,7 @@ export function ProductionCapacityIcon() { @@ -62,12 +62,12 @@ export function ROISummaryIcon() { @@ -118,12 +118,12 @@ export function ROISummaryProductName() { > diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/components/ActionsList.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/components/ActionsList.tsx index 09017de..8aae3b2 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/components/ActionsList.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/components/ActionsList.tsx @@ -35,7 +35,7 @@ const ActionsList: React.FC = ({ const handleRenameAction = (newName: string) => { if (!selectedAction.actionId) return; - const event = renameAction(selectedAction.actionId, newName); + const event = renameAction(selectedProduct.productId, selectedAction.actionId, newName); if (event) { upsertProductOrEventApi({ diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx index f68b6b8..a6a1fa9 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/conveyorMechanics.tsx @@ -72,7 +72,7 @@ function ConveyorMechanics() { const validOption = option as | "default" | "spawn" | "swap" | "delay" | "despawn"; setActiveOption(validOption); - const event = updateAction(selectedPointData.action.actionUuid, { + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { actionType: validOption, }); @@ -88,7 +88,7 @@ function ConveyorMechanics() { const handleRenameAction = (newName: string) => { if (!selectedEventData || !selectedPointData) return; - const event = updateAction(selectedPointData.action.actionUuid, { actionName: newName }); + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { actionName: newName }); if (event) { updateBackend( @@ -102,7 +102,7 @@ function ConveyorMechanics() { const handleSpawnCountChange = (value: string) => { if (!selectedEventData || !selectedPointData) return; - const event = updateAction(selectedPointData.action.actionUuid, { + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { spawnCount: value === "inherit" ? "inherit" : parseFloat(value), }); @@ -118,7 +118,7 @@ function ConveyorMechanics() { const handleSpawnIntervalChange = (value: string) => { if (!selectedEventData || !selectedPointData) return; - const event = updateAction(selectedPointData.action.actionUuid, { + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { spawnInterval: value === "inherit" ? "inherit" : parseFloat(value), }); @@ -134,7 +134,7 @@ function ConveyorMechanics() { const handleMaterialSelect = (material: string) => { if (!selectedEventData || !selectedPointData) return; - const event = updateAction(selectedPointData.action.actionUuid, { material }); + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { material }); if (event) { updateBackend( @@ -148,7 +148,7 @@ function ConveyorMechanics() { const handleDelayChange = (value: string) => { if (!selectedEventData || !selectedPointData) return; - const event = updateAction(selectedPointData.action.actionUuid, { + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { delay: value === "inherit" ? "inherit" : parseFloat(value), }); @@ -271,7 +271,7 @@ function 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 index 993c46b..3e11e54 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/machineMechanics.tsx @@ -6,6 +6,7 @@ import { useSelectedEventData, useSelectedProduct } from "../../../../../../stor import { useProductStore } from "../../../../../../store/simulation/useProductStore"; import ProcessAction from "../actions/ProcessAction"; import ActionsList from "../components/ActionsList"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi"; function MachineMechanics() { const [activeOption, setActiveOption] = useState<"default" | "process">("default"); @@ -14,6 +15,9 @@ function MachineMechanics() { const { getPointByUuid, updateAction } = useProductStore(); const { selectedProduct } = useSelectedProduct(); + const email = localStorage.getItem('email') + const organization = (email!.split("@")[1]).split(".")[0]; + useEffect(() => { if (selectedEventData) { const point = getPointByUuid( @@ -28,31 +32,54 @@ function MachineMechanics() { } }, [selectedProduct, selectedEventData, getPointByUuid]); + const updateBackend = ( + productName: string, + productId: string, + organization: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productId: productId, + organization: organization, + eventDatas: eventData + }) + } + const handleActionTypeChange = (option: string) => { if (!selectedEventData || !selectedPointData) return; const validOption = option as "process"; setActiveOption(validOption); - updateAction(selectedPointData.action.actionUuid, { + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { actionType: validOption, }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } }; const handleRenameAction = (newName: string) => { if (!selectedPointData) return; - updateAction(selectedPointData.action.actionUuid, { actionName: newName }); + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { actionName: newName }); }; const handleProcessTimeChange = (value: string) => { if (!selectedPointData) return; - updateAction(selectedPointData.action.actionUuid, { + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { processTime: parseFloat(value), }); }; const handleMaterialSelect = (material: string) => { if (!selectedPointData) return; - updateAction(selectedPointData.action.actionUuid, { + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { swapMaterial: material, }); }; diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx index e56741f..df5c358 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/roboticArmMechanics.tsx @@ -59,7 +59,7 @@ function RoboticArmMechanics() { const handleRenameAction = (newName: string) => { if (!selectedAction.actionId) return; - const event = updateAction(selectedAction.actionId, { actionName: newName }); + const event = updateAction(selectedProduct.productId, selectedAction.actionId, { actionName: newName }); if (selectedPointData) { const updatedActions = selectedPointData.actions.map((action) => @@ -101,7 +101,7 @@ function RoboticArmMechanics() { if (!selectedAction.actionId || !selectedPointData) return; const [x, y, z] = value.split(",").map(Number); - const event = updateAction(selectedAction.actionId, { + const event = updateAction(selectedProduct.productId, selectedAction.actionId, { process: { startPoint: [x, y, z] as [number, number, number], endPoint: selectedPointData.actions.find((a) => a.actionUuid === selectedAction.actionId)?.process.endPoint || null, @@ -122,7 +122,7 @@ function RoboticArmMechanics() { if (!selectedAction.actionId || !selectedPointData) return; const [x, y, z] = value.split(",").map(Number); - const event = updateAction(selectedAction.actionId, { + const event = updateAction(selectedProduct.productId, selectedAction.actionId, { process: { startPoint: selectedPointData.actions.find((a) => a.actionUuid === selectedAction.actionId)?.process.startPoint || null, endPoint: [x, y, z] as [number, number, number], @@ -181,7 +181,7 @@ function RoboticArmMechanics() { const handleDeleteAction = (actionUuid: string) => { if (!selectedPointData) return; - const event = removeAction(actionUuid); + const event = removeAction(selectedProduct.productId, actionUuid); if (event) { updateBackend( diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx index c85da9f..3273ce4 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/storageMechanics.tsx @@ -6,6 +6,7 @@ import { useSelectedEventData, useSelectedProduct } from "../../../../../../stor import { useProductStore } from "../../../../../../store/simulation/useProductStore"; import StorageAction from "../actions/StorageAction"; import ActionsList from "../components/ActionsList"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi"; function StorageMechanics() { const [activeOption, setActiveOption] = useState<"default" | "store" | "spawn">("default"); @@ -14,6 +15,9 @@ function StorageMechanics() { const { getPointByUuid, updateAction } = useProductStore(); const { selectedProduct } = useSelectedProduct(); + const email = localStorage.getItem('email') + const organization = (email!.split("@")[1]).split(".")[0]; + useEffect(() => { if (selectedEventData) { const point = getPointByUuid( @@ -28,26 +32,67 @@ function StorageMechanics() { } }, [selectedProduct, selectedEventData, getPointByUuid]); + const updateBackend = ( + productName: string, + productId: string, + organization: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productId: productId, + organization: organization, + eventDatas: eventData + }) + } + const handleActionTypeChange = (option: string) => { if (!selectedEventData || !selectedPointData) return; const validOption = option as "store" | "spawn"; setActiveOption(validOption); - updateAction(selectedPointData.action.actionUuid, { + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { actionType: validOption, }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } }; const handleRenameAction = (newName: string) => { if (!selectedPointData) return; - updateAction(selectedPointData.action.actionUuid, { actionName: newName }); + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { actionName: newName }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } }; const handleCapacityChange = (value: string) => { if (!selectedPointData) return; - updateAction(selectedPointData.action.actionUuid, { + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { storageCapacity: parseInt(value), }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } }; // Get current values from store diff --git a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx index 28eb61f..7839168 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/mechanics/vehicleMechanics.tsx @@ -10,6 +10,7 @@ import { import { useProductStore } from "../../../../../../store/simulation/useProductStore"; import TravelAction from "../actions/TravelAction"; import ActionsList from "../components/ActionsList"; +import { upsertProductOrEventApi } from "../../../../../../services/simulation/UpsertProductOrEventApi"; function VehicleMechanics() { const [activeOption, setActiveOption] = useState<"default" | "travel">("default"); @@ -18,8 +19,11 @@ function VehicleMechanics() { const { getPointByUuid, getEventByModelUuid, updateEvent, updateAction } = useProductStore(); const { selectedProduct } = useSelectedProduct(); + const email = localStorage.getItem('email') + const organization = (email!.split("@")[1]).split(".")[0]; + useEffect(() => { - if (selectedEventData) { + if (selectedEventData && selectedEventData.data.type === 'vehicle') { const point = getPointByUuid( selectedProduct.productId, selectedEventData.data.modelUuid, @@ -33,11 +37,34 @@ function VehicleMechanics() { } }, [selectedProduct, selectedEventData, getPointByUuid]); + const updateBackend = ( + productName: string, + productId: string, + organization: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productId: productId, + organization: organization, + eventDatas: eventData + }) + } + const handleSpeedChange = (value: string) => { if (!selectedEventData) return; - updateEvent(selectedProduct.productId, selectedEventData.data.modelUuid, { + const event = updateEvent(selectedProduct.productId, selectedEventData.data.modelUuid, { speed: parseFloat(value), }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } }; const handleActionTypeChange = (option: string) => { @@ -45,28 +72,64 @@ function VehicleMechanics() { const validOption = option as "travel"; setActiveOption(validOption); - updateAction(selectedPointData.action.actionUuid, { + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { actionType: validOption, }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } }; const handleRenameAction = (newName: string) => { if (!selectedPointData) return; - updateAction(selectedPointData.action.actionUuid, { actionName: newName }); + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { actionName: newName }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } }; const handleLoadCapacityChange = (value: string) => { if (!selectedPointData) return; - updateAction(selectedPointData.action.actionUuid, { + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { loadCapacity: parseFloat(value), }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } }; const handleUnloadDurationChange = (value: string) => { if (!selectedPointData) return; - updateAction(selectedPointData.action.actionUuid, { + const event = updateAction(selectedProduct.productId, selectedPointData.action.actionUuid, { unLoadDuration: parseFloat(value), }); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } }; const handlePickPointChange = (value: string) => { 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 1434446..86e4e7b 100644 --- a/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx +++ b/app/src/components/layout/sidebarRight/properties/eventProperties/trigger/Trigger.tsx @@ -1,137 +1,132 @@ -import React, { useRef, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { - AddIcon, - RemoveIcon, - ResizeHeightIcon, + AddIcon, + RemoveIcon, + ResizeHeightIcon, } from "../../../../../icons/ExportCommonIcons"; import LabledDropdown from "../../../../../ui/inputs/LabledDropdown"; import RenameInput from "../../../../../ui/inputs/RenameInput"; import { handleResize } from "../../../../../../functions/handleResizePannel"; +import { useProductStore } from "../../../../../../store/simulation/useProductStore"; +import { useSelectedProduct } from "../../../../../../store/simulation/useSimulationStore"; -const Trigger: React.FC = () => { - // State to hold the list of triggers - const [triggers, setTriggers] = useState(["Trigger 1"]); - const [selectedTrigger, setSelectedTrigger] = useState("Trigger 1"); - const [activeOption, setActiveOption] = useState("onComplete"); - const triggersContainerRef = useRef(null); +type TriggerProps = { + selectedPointData?: PointsScheme | undefined; + type?: 'Conveyor' | 'Vehicle' | 'RoboticArm' | 'Machine' | 'StorageUnit'; +}; - // States for dropdowns - const [triggeredModel, setTriggeredModel] = useState([]); - const [triggeredPoint, setTriggeredPoint] = useState([]); - const [triggeredAction, setTriggeredAction] = useState([]); +const Trigger = ({ selectedPointData, type }: TriggerProps) => { + const [currentAction, setCurrentAction] = useState(); + const { selectedProduct } = useSelectedProduct(); + const { getActionByUuid } = useProductStore(); + const [triggers, setTriggers] = useState([]); + const [selectedTrigger, setSelectedTrigger] = useState(); + const [activeOption, setActiveOption] = useState("onComplete"); + const triggersContainerRef = useRef(null); - // Function to handle adding a new trigger - const addTrigger = (): void => { - const newTrigger = `Trigger ${triggers.length + 1}`; - setTriggers([...triggers, newTrigger]); // Add new trigger to the state + useEffect(() => { + if (!selectedPointData) return; + if (type === 'Conveyor' || type === 'Vehicle' || type === 'Machine' || type === 'StorageUnit') { + setCurrentAction((selectedPointData as ConveyorPointSchema).action.actionUuid); + } + }, [selectedPointData]); - // Initialize the states for the new trigger - setTriggeredModel([...triggeredModel, ""]); - setTriggeredPoint([...triggeredPoint, ""]); - setTriggeredAction([...triggeredAction, ""]); - }; + useEffect(() => { + if (!currentAction || !selectedProduct) return; + const action = getActionByUuid(selectedProduct.productId, currentAction); + setTriggers(action?.triggers || []); + setSelectedTrigger(action?.triggers[0] || undefined); + }, [currentAction, selectedProduct]); - // Function to handle removing a trigger - const removeTrigger = (index: number): void => { - setTriggers(triggers.filter((_, i) => i !== index)); // Remove trigger by index - setTriggeredModel(triggeredModel.filter((_, i) => i !== index)); - setTriggeredPoint(triggeredPoint.filter((_, i) => i !== index)); - setTriggeredAction(triggeredAction.filter((_, i) => i !== index)); - }; + const addTrigger = (): void => { + }; - return ( -
-
-
Trigger
- -
-
-
-
- {triggers.map((trigger: any, index: number) => ( -
setSelectedTrigger(trigger)} - > - - {triggers.length > 1 && ( - - )} -
- ))} -
- +
+
+
+
+ {triggers.map((trigger) => ( +
setSelectedTrigger(trigger)} + > + + {triggers.length > 1 && ( + + )} +
+ ))} +
+ +
+
+
{selectedTrigger?.triggerName}
+ setActiveOption(option)} + /> +
+ { }} + /> + { }} + /> + { }} + /> +
+
+
-
-
{selectedTrigger}
- setActiveOption(option)} - /> -
- { - const newModel = [...triggeredModel]; - newModel[0] = option; - setTriggeredModel(newModel); - }} - /> - { - const newPoint = [...triggeredPoint]; - newPoint[0] = option; - setTriggeredPoint(newPoint); - }} - /> - { - const newAction = [...triggeredAction]; - newAction[0] = option; - setTriggeredAction(newAction); - }} - /> -
-
-
-
- ); + ); }; export default Trigger; diff --git a/app/src/components/ui/analysis/ROISummary.tsx b/app/src/components/ui/analysis/ROISummary.tsx index 92628af..8be74e8 100644 --- a/app/src/components/ui/analysis/ROISummary.tsx +++ b/app/src/components/ui/analysis/ROISummary.tsx @@ -7,6 +7,7 @@ import { SonarCrownIcon, } from "../../icons/analysis"; import SemiCircleProgress from "./SemiCircleProgress"; +import { ArrowIcon } from "../../icons/ExportCommonIcons"; const ROISummary = ({ roiSummaryData = { @@ -129,7 +130,7 @@ const ROISummary = ({ - {isTableOpen ? "⌵" : "⌵"} +
{ - const progress = 50; +const SemiCircleProgress = ({ progress = 10 }) => { const clampedProgress = Math.min(Math.max(progress, 0), 100); - const gradientProgress = clampedProgress * 0.5; + const radius = 80; + const strokeWidth = 20; + const circumference = Math.PI * radius; + const strokeDashoffset = + circumference - (clampedProgress / 100) * circumference; return ( -
-
-
-
+
+ + {/* Background track */} + + {/* Progress track */} + +
{clampedProgress}%
Years
-
you're on track to hit it by July 2029
); }; diff --git a/app/src/components/ui/log/LogList.tsx b/app/src/components/ui/log/LogList.tsx index 1b39e2f..fad277c 100644 --- a/app/src/components/ui/log/LogList.tsx +++ b/app/src/components/ui/log/LogList.tsx @@ -2,7 +2,6 @@ import React, { useState } from "react"; import { LogListIcon, - TickIcon, LogInfoIcon, WarningIcon, ErrorIcon, @@ -38,8 +37,17 @@ const LogList: React.FC = () => { : [...logs].filter((log) => log.type === selectedTab).reverse(); return ( -
-
+ // eslint-disable-next-line +
setIsLogListVisible(false)} + > +
{ + e.stopPropagation(); + }} + >
@@ -47,9 +55,9 @@ const LogList: React.FC = () => {
Log List
-
setIsLogListVisible(false)}> - {/* */}X -
+
{/* Tabs */} @@ -69,13 +77,12 @@ const LogList: React.FC = () => {
{filteredLogs.map((log) => (
-
- -
{getLogIcon(log.type)}
-
- [{formatTimestamp(log.timestamp)}] [{log.type.toUpperCase()}]{" "} - {log.message} +
+
{log.message}
+
+ {formatTimestamp(log.timestamp)} +
))} diff --git a/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts b/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts index a351d73..ee5c283 100644 --- a/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts +++ b/app/src/modules/builder/IntialLoad/loadInitialFloorItems.ts @@ -187,7 +187,7 @@ function processLoadedModel( }, ]); - if (item.eventData.type === "vehicle") { + if (item.eventData.type === "Vehicle") { const vehicleEvent: VehicleEventSchema = { modelUuid: item.modelUuid, modelName: item.modelName, @@ -202,11 +202,11 @@ function processLoadedModel( rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0], action: { actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Vehicle Action", + actionName: "Action 1", actionType: "travel", unLoadDuration: 5, loadCapacity: 10, - steeringAngle:0, + steeringAngle: 0, pickUpPoint: null, unLoadPoint: null, triggers: [] @@ -254,7 +254,7 @@ function processLoadedModel( rotation: [item.eventData.point?.rotation[0] || 0, item.eventData.point?.rotation[1] || 0, item.eventData.point?.rotation[2] || 0], action: { actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Process Action", + actionName: "Action 1", actionType: "process", processTime: 10, swapMaterial: "material-id", @@ -279,7 +279,7 @@ function processLoadedModel( actions: [ { actionUuid: THREE.MathUtils.generateUUID(), - actionName: "Pick and Place", + actionName: "Action 1", actionType: "pickAndPlace", process: { startPoint: [0, 0, 0], diff --git a/app/src/modules/builder/geomentries/assets/addAssetModel.ts b/app/src/modules/builder/geomentries/assets/addAssetModel.ts index c718ec3..64656a0 100644 --- a/app/src/modules/builder/geomentries/assets/addAssetModel.ts +++ b/app/src/modules/builder/geomentries/assets/addAssetModel.ts @@ -186,22 +186,59 @@ async function handleModelLoad( 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: 'Default Material', - delay: 0, - spawnInterval: 5, - spawnCount: 1, - triggers: [] + points: data.points.map((point: THREE.Vector3, index: number) => { + const triggers: TriggerSchema[] = []; + + if (data.points && index < data.points.length - 1) { + triggers.push({ + triggerUuid: THREE.MathUtils.generateUUID(), + triggerName: `Trigger 1`, + triggerType: "onComplete", + delay: 0, + triggeredAsset: { + triggeredModel: { + modelName: newFloorItem.modelName, + modelUuid: newFloorItem.modelUuid + }, + triggeredPoint: { + pointName: `Point`, + pointUuid: "" + }, + triggeredAction: { + actionName: `Action 1`, + actionUuid: "" + } + } + }); } - })) + + return { + uuid: THREE.MathUtils.generateUUID(), + position: [point.x, point.y, point.z], + rotation: [0, 0, 0], + action: { + actionUuid: THREE.MathUtils.generateUUID(), + actionName: `Action 1`, + actionType: 'default', + material: 'Default Material', + delay: 0, + spawnInterval: 5, + spawnCount: 1, + triggers: triggers + } + }; + }) }; + + for (let i = 0; i < ConveyorEvent.points.length - 1; i++) { + const currentPoint = ConveyorEvent.points[i]; + const nextPoint = ConveyorEvent.points[i + 1]; + + if (currentPoint.action.triggers.length > 0) { + currentPoint.action.triggers[0].triggeredAsset!.triggeredPoint.pointUuid = nextPoint.uuid; + currentPoint.action.triggers[0].triggeredAsset!.triggeredAction!.actionUuid = nextPoint.action.actionUuid; + } + } addEvent(ConveyorEvent); eventData.points = ConveyorEvent.points.map(point => ({ uuid: point.uuid, @@ -228,7 +265,7 @@ async function handleModelLoad( actionType: "travel", unLoadDuration: 5, loadCapacity: 10, - steeringAngle:0, + steeringAngle: 0, pickUpPoint: null, unLoadPoint: null, triggers: [] diff --git a/app/src/modules/scene/controls/selectionControls/moveControls.tsx b/app/src/modules/scene/controls/selectionControls/moveControls.tsx index d371806..cc9ce50 100644 --- a/app/src/modules/scene/controls/selectionControls/moveControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/moveControls.tsx @@ -9,6 +9,7 @@ import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifie import { useEventsStore } from "../../../../store/simulation/useEventsStore"; import { useProductStore } from "../../../../store/simulation/useProductStore"; import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore"; +import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi"; function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, rotatedObjects, setRotatedObjects, boundingBoxRef }: any) { const { camera, controls, gl, scene, pointer, raycaster } = useThree(); @@ -16,10 +17,28 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); + const { selectedProduct } = useSelectedProduct(); const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore(); const itemsData = useRef([]); + const email = localStorage.getItem('email') + const organization = (email!.split("@")[1]).split(".")[0]; + + const updateBackend = ( + productName: string, + productId: string, + organization: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productId: productId, + organization: organization, + eventDatas: eventData + }) + } + useEffect(() => { if (!camera || !scene || toggleView || !itemsGroupRef.current) return; @@ -190,10 +209,19 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje }) } if (productData) { - useProductStore.getState().updateEvent(useSelectedProduct.getState().selectedProduct.productId, obj.userData.modelUuid, { + const event = useProductStore.getState().updateEvent(useSelectedProduct.getState().selectedProduct.productId, obj.userData.modelUuid, { position: [worldPosition.x, worldPosition.y, worldPosition.z], rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], }) + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } } } @@ -203,9 +231,6 @@ function MoveControls({ movedObjects, setMovedObjects, itemsGroupRef, copiedObje return updatedItems; }); - const email = localStorage.getItem("email"); - const organization = email ? email.split("@")[1].split(".")[0] : "default"; - //REST // await setFloorItemApi( diff --git a/app/src/modules/scene/controls/selectionControls/rotateControls.tsx b/app/src/modules/scene/controls/selectionControls/rotateControls.tsx index 08667b4..7ea7045 100644 --- a/app/src/modules/scene/controls/selectionControls/rotateControls.tsx +++ b/app/src/modules/scene/controls/selectionControls/rotateControls.tsx @@ -8,6 +8,7 @@ import * as Types from "../../../../types/world/worldTypes"; import { useEventsStore } from "../../../../store/simulation/useEventsStore"; import { useProductStore } from "../../../../store/simulation/useProductStore"; import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore"; +import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi"; function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMovedObjects, itemsGroupRef, copiedObjects, setCopiedObjects, pastedObjects, setpastedObjects, duplicatedObjects, setDuplicatedObjects, selectionGroup, boundingBoxRef }: any) { const { camera, controls, gl, scene, pointer, raycaster } = useThree(); @@ -15,10 +16,28 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo const { toggleView } = useToggleView(); const { selectedAssets, setSelectedAssets } = useSelectedAssets(); + const { selectedProduct } = useSelectedProduct(); const { floorItems, setFloorItems } = useFloorItems(); const { socket } = useSocketStore(); const itemsData = useRef([]); + const email = localStorage.getItem('email') + const organization = (email!.split("@")[1]).split(".")[0]; + + const updateBackend = ( + productName: string, + productId: string, + organization: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productId: productId, + organization: organization, + eventDatas: eventData + }) + } + const prevPointerPosition = useRef(null); useEffect(() => { @@ -190,10 +209,19 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo }) } if (productData) { - useProductStore.getState().updateEvent(useSelectedProduct.getState().selectedProduct.productId, obj.userData.modelUuid, { + const event = useProductStore.getState().updateEvent(useSelectedProduct.getState().selectedProduct.productId, obj.userData.modelUuid, { position: [worldPosition.x, worldPosition.y, worldPosition.z], rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z], }) + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } } } @@ -203,9 +231,6 @@ function RotateControls({ rotatedObjects, setRotatedObjects, movedObjects, setMo return updatedItems; }); - const email = localStorage.getItem("email"); - const organization = email ? email.split("@")[1].split(".")[0] : "default"; - //REST // await setFloorItemApi( diff --git a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx index c6ec316..e178a81 100644 --- a/app/src/modules/simulation/events/points/creator/pointsCreator.tsx +++ b/app/src/modules/simulation/events/points/creator/pointsCreator.tsx @@ -1,22 +1,29 @@ import React, { useEffect, useRef, useState } from "react"; import * as THREE from "three"; import { useEventsStore } from "../../../../../store/simulation/useEventsStore"; -import useModuleStore from "../../../../../store/useModuleStore"; +import useModuleStore, { useSubModuleStore } from "../../../../../store/useModuleStore"; import { TransformControls } from "@react-three/drei"; import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys"; import { useSelectedEventSphere, useSelectedEventData, + useIsDragging, + useIsRotating, } from "../../../../../store/simulation/useSimulationStore"; +import { useThree } from "@react-three/fiber"; function PointsCreator() { + const { gl, raycaster, scene, pointer, camera } = useThree(); + const { subModule } = useSubModuleStore(); 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 { selectedEventData, setSelectedEventData, clearSelectedEventData } = useSelectedEventData(); + const { setSelectedEventData, clearSelectedEventData } = useSelectedEventData(); + const { isDragging } = useIsDragging(); + const { isRotating } = useIsRotating(); useEffect(() => { if (selectedEventSphere) { @@ -72,6 +79,53 @@ function PointsCreator() { } }; + useEffect(() => { + const canvasElement = gl.domElement; + + let drag = false; + let isMouseDown = false; + + const onMouseDown = () => { + isMouseDown = true; + drag = false; + }; + + const onMouseUp = () => { + if (selectedEventSphere && !drag) { + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster + .intersectObjects(scene.children, true) + .filter( + (intersect) => + intersect.object.name === ('Event-Sphere') + ); + if (intersects.length === 0) { + clearSelectedEventSphere(); + setTransformMode(null); + } + } + } + + const onMouseMove = () => { + if (isMouseDown) { + drag = true; + } + }; + + if (subModule === 'mechanics') { + canvasElement.addEventListener("mousedown", onMouseDown); + canvasElement.addEventListener("mouseup", onMouseUp); + canvasElement.addEventListener("mousemove", onMouseMove); + } + + return () => { + canvasElement.removeEventListener("mousedown", onMouseDown); + canvasElement.removeEventListener("mouseup", onMouseUp); + canvasElement.removeEventListener("mousemove", onMouseMove); + }; + + }, [gl, subModule, selectedEventSphere]); + return ( <> {activeModule === "simulation" && ( @@ -92,12 +146,6 @@ function PointsCreator() { sphereRefs.current[point.uuid] ); }} - onPointerMissed={() => { - if (selectedEventData?.data.type !== 'vehicle') { - clearSelectedEventSphere(); - } - setTransformMode(null); - }} key={`${i}-${j}`} position={new THREE.Vector3(...point.position)} // rotation={new THREE.Euler(...point.rotation)} @@ -122,10 +170,6 @@ function PointsCreator() { sphereRefs.current[event.point.uuid] ); }} - onPointerMissed={() => { - clearSelectedEventSphere(); - setTransformMode(null); - }} position={new THREE.Vector3(...event.point.position)} // rotation={new THREE.Euler(...event.point.rotation)} userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }} @@ -148,10 +192,6 @@ function PointsCreator() { sphereRefs.current[event.point.uuid] ); }} - onPointerMissed={() => { - clearSelectedEventSphere(); - setTransformMode(null); - }} position={new THREE.Vector3(...event.point.position)} // rotation={new THREE.Euler(...event.point.rotation)} userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }} @@ -174,10 +214,6 @@ function PointsCreator() { sphereRefs.current[event.point.uuid] ); }} - onPointerMissed={() => { - clearSelectedEventSphere(); - setTransformMode(null); - }} position={new THREE.Vector3(...event.point.position)} // rotation={new THREE.Euler(...event.point.rotation)} userData={{ modelUuid: event.modelUuid, pointUuid: event.point.uuid }} diff --git a/app/src/modules/simulation/machine/machine.tsx b/app/src/modules/simulation/machine/machine.tsx index 32c3b8e..e9d2dea 100644 --- a/app/src/modules/simulation/machine/machine.tsx +++ b/app/src/modules/simulation/machine/machine.tsx @@ -1,7 +1,43 @@ -import React from 'react' +import React, { useEffect } from 'react' import MachineInstances from './instances/machineInstances' +import { useMachineStore } from '../../../store/simulation/useMachineStore' +import { useSelectedProduct } from '../../../store/simulation/useSimulationStore'; function Machine() { + const { addMachine, addCurrentAction, removeMachine } = useMachineStore(); + const { selectedProduct } = useSelectedProduct(); + + const machineSample: MachineEventSchema[] = [ + { + modelUuid: "machine-1234-5678-9012", + modelName: "CNC Milling Machine", + position: [10, 0, 5], + rotation: [0, 0, 0], + state: "idle", + type: "machine", + point: { + uuid: "machine-point-9876-5432-1098", + position: [10, 0.5, 5.2], + rotation: [0, 0, 0], + action: { + actionUuid: "machine-action-2468-1357-8024", + actionName: "Metal Processing", + actionType: "process", + processTime: 10, + swapMaterial: "steel", + triggers: [] + } + } + } + ]; + + useEffect(() => { + removeMachine(machineSample[0].modelUuid); + addMachine(selectedProduct.productId, machineSample[0]); + + // addCurrentAction(machineSample[0].modelUuid, machineSample[0].point.action.actionUuid); + }, []) + return ( <> diff --git a/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx b/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx index 9eececc..715aa37 100644 --- a/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx +++ b/app/src/modules/simulation/products/events/addOrRemoveEventsInProducts.tsx @@ -53,7 +53,17 @@ function AddOrRemoveEventsInProducts() { const canvasElement = gl.domElement; if (!canvasElement) return; - let intersects = raycaster.intersectObjects(scene.children, true); + const intersects = raycaster + .intersectObjects(scene.children, true) + .filter( + (intersect) => + !intersect.object.name.includes("Roof") && + !intersect.object.name.includes("MeasurementReference") && + !intersect.object.name.includes("agv-collider") && + !(intersect.object.type === "GridHelper") && + !(intersect.object?.parent?.name.includes('zones')) && + !(intersect.object?.parent?.name.includes('Zone')) + ); 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; @@ -116,6 +126,7 @@ function AddOrRemoveEventsInProducts() { }; }, [gl, subModule, selectedProduct, selectedAsset]); + return ( <> ) diff --git a/app/src/modules/simulation/products/products.tsx b/app/src/modules/simulation/products/products.tsx index ee1ac42..921dd96 100644 --- a/app/src/modules/simulation/products/products.tsx +++ b/app/src/modules/simulation/products/products.tsx @@ -7,7 +7,7 @@ import { upsertProductOrEventApi } from '../../../services/simulation/UpsertProd import { getAllProductsApi } from '../../../services/simulation/getallProductsApi'; function Products() { - const { products, addProduct, setProducts } = useProductStore(); + const { addProduct, setProducts } = useProductStore(); const { setSelectedProduct } = useSelectedProduct(); useEffect(() => { @@ -27,9 +27,6 @@ function Products() { }) }, []) - useEffect(() => { - }, []) - return ( <> diff --git a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx index 6bde587..af3ffe3 100644 --- a/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx +++ b/app/src/modules/simulation/roboticArm/instances/armInstance/roboticArmInstance.tsx @@ -170,8 +170,8 @@ function RoboticArmInstance({ robot }: { robot: ArmBotStatus }) { } } const logStatus = (id: string, status: string) => { - // console.log(id + "," + status); - console.log( status); + // + } return ( diff --git a/app/src/modules/simulation/simulation.tsx b/app/src/modules/simulation/simulation.tsx index 5ca0ec5..757a9ef 100644 --- a/app/src/modules/simulation/simulation.tsx +++ b/app/src/modules/simulation/simulation.tsx @@ -23,7 +23,7 @@ function Simulation() { }, [events]) useEffect(() => { - // console.log('products: ', products); + console.log('products: ', products); }, [products]) return ( diff --git a/app/src/modules/simulation/triggers/connector/triggerConnector.tsx b/app/src/modules/simulation/triggers/connector/triggerConnector.tsx index efefa0c..dcb46f4 100644 --- a/app/src/modules/simulation/triggers/connector/triggerConnector.tsx +++ b/app/src/modules/simulation/triggers/connector/triggerConnector.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from "react"; -import { useThree } from "@react-three/fiber"; +import { useFrame, useThree } from "@react-three/fiber"; import * as THREE from "three"; import { useSubModuleStore } from "../../../../store/useModuleStore"; import { useSelectedAsset } from "../../../../store/simulation/useSimulationStore"; @@ -7,21 +7,28 @@ import { useProductStore } from "../../../../store/simulation/useProductStore"; import { useEventsStore } from "../../../../store/simulation/useEventsStore"; import { useSelectedProduct } from "../../../../store/simulation/useSimulationStore"; import { handleAddEventToProduct } from "../../events/points/functions/handleAddEventToProduct"; +import { QuadraticBezierLine } from "@react-three/drei"; +import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertProductOrEventApi"; +import { useDeleteTool } from "../../../../store/store"; interface ConnectionLine { id: string; - start: THREE.Vector3; - end: THREE.Vector3; - mid: THREE.Vector3; + startPointUuid: string; + endPointUuid: string; trigger: TriggerSchema; } function TriggerConnector() { - const { gl, raycaster, scene } = useThree(); + const { gl, raycaster, scene, pointer, camera } = useThree(); const { subModule } = useSubModuleStore(); - const { products, getPointByUuid, getIsEventInProduct, getActionByUuid, addTrigger, addEvent, getEventByModelUuid } = useProductStore(); + const { products, getPointByUuid, getIsEventInProduct, getActionByUuid, addTrigger, removeTrigger, addEvent, getEventByModelUuid, getProductById } = useProductStore(); const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); const { selectedProduct } = useSelectedProduct(); + const [hoveredLineKey, setHoveredLineKey] = useState(null); + const groupRefs = useRef>({}); + const [helperlineColor, setHelperLineColor] = useState("red"); + const [currentLine, setCurrentLine] = useState<{ start: THREE.Vector3; end: THREE.Vector3; mid: THREE.Vector3; } | null>(null); + const { deleteTool } = useDeleteTool(); const [firstSelectedPoint, setFirstSelectedPoint] = useState<{ productId: string; @@ -32,52 +39,99 @@ function TriggerConnector() { const [connections, setConnections] = useState([]); + const email = localStorage.getItem('email') + const organization = (email!.split("@")[1]).split(".")[0]; + + const updateBackend = ( + productName: string, + productId: string, + organization: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productId: productId, + organization: organization, + eventDatas: eventData + }) + } + useEffect(() => { const newConnections: ConnectionLine[] = []; - products.forEach(product => { - product.eventDatas.forEach(event => { - if ('points' in event) { - event.points.forEach(point => { - if ('action' in point && point.action?.triggers) { - point.action.triggers.forEach(trigger => { - if (trigger.triggeredAsset) { - const targetPoint = getPointByUuid( - product.productId, - trigger.triggeredAsset.triggeredModel.modelUuid, - trigger.triggeredAsset.triggeredPoint.pointUuid - ); + const product = getProductById(selectedProduct.productId); + if (!product || products.length === 0) return; - if (targetPoint) { - const startPos = new THREE.Vector3(...point.position); - const endPos = new THREE.Vector3(...targetPoint.position); - const midPos = new THREE.Vector3() - .addVectors(startPos, endPos) - .multiplyScalar(0.5) - .add(new THREE.Vector3(0, 2, 0)); - - newConnections.push({ - id: `${point.uuid}-${targetPoint.uuid}-${trigger.triggerUuid}`, - start: startPos, - end: endPos, - mid: midPos, - trigger - }); - } - } + product.eventDatas.forEach(event => { + // Handle Conveyor points + if (event.type === "transfer" && 'points' in event) { + event.points.forEach(point => { + if (point.action?.triggers) { + point.action.triggers.forEach(trigger => { + if (trigger.triggeredAsset) { + newConnections.push({ + id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, + startPointUuid: point.uuid, + endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + trigger + }); + } + }); + } + }); + } + // Handle Vehicle point + else if (event.type === "vehicle" && 'point' in event) { + const point = event.point; + if (point.action?.triggers) { + point.action.triggers.forEach(trigger => { + if (trigger.triggeredAsset) { + newConnections.push({ + id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, + startPointUuid: point.uuid, + endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + trigger }); } }); } - }); + } + // Handle Robotic Arm points + else if (event.type === "roboticArm" && 'point' in event) { + const point = event.point; + point.actions?.forEach(action => { + action.triggers?.forEach(trigger => { + if (trigger.triggeredAsset) { + newConnections.push({ + id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, + startPointUuid: point.uuid, + endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + trigger + }); + } + }); + }); + } + // Handle Machine point + else if (event.type === "machine" && 'point' in event) { + const point = event.point; + if (point.action?.triggers) { + point.action.triggers.forEach(trigger => { + if (trigger.triggeredAsset) { + newConnections.push({ + id: `${point.uuid}-${trigger.triggeredAsset.triggeredPoint.pointUuid}-${trigger.triggerUuid}`, + startPointUuid: point.uuid, + endPointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid, + trigger + }); + } + }); + } + } }); setConnections(newConnections); - }, [products]); - - useEffect(() => { - console.log('connections: ', connections); - }, connections) + }, [products, selectedProduct.productId]); useEffect(() => { const canvasElement = gl.domElement; @@ -111,15 +165,31 @@ function TriggerConnector() { if (drag) return; evt.preventDefault(); - const intersects = raycaster.intersectObjects(scene.children, true); - if (intersects.length === 0) return; + const intersects = raycaster + .intersectObjects(scene.children, true) + .filter( + (intersect) => + intersect.object.name === ('Event-Sphere') + ); + if (intersects.length === 0) { + setFirstSelectedPoint(null); + return; + }; const currentObject = intersects[0].object; - if (!currentObject || currentObject.name !== 'Event-Sphere') return; + if (!currentObject || currentObject.name !== 'Event-Sphere') { + setFirstSelectedPoint(null); + return; + }; const modelUuid = currentObject.userData.modelUuid; const pointUuid = currentObject.userData.pointUuid; + if (firstSelectedPoint && firstSelectedPoint.pointUuid === pointUuid) { + setFirstSelectedPoint(null); + return; + } + if (selectedProduct && getIsEventInProduct(selectedProduct.productId, modelUuid)) { const point = getPointByUuid( @@ -128,7 +198,12 @@ function TriggerConnector() { pointUuid ); - if (!point) return; + const event = getEventByModelUuid(selectedProduct.productId, modelUuid); + + if (!point || !event) { + setFirstSelectedPoint(null); + return; + }; let actionUuid: string | undefined; if ('action' in point && point.action) { @@ -152,12 +227,12 @@ function TriggerConnector() { delay: 0, triggeredAsset: { triggeredModel: { - modelName: currentObject.parent?.parent?.name || 'Unknown', + modelName: event.modelName || 'Unknown', modelUuid: modelUuid }, triggeredPoint: { - pointName: currentObject.name, - pointUuid: pointUuid + pointName: 'Point', + pointUuid: point.uuid }, triggeredAction: actionUuid ? { actionName: getActionByUuid(selectedProduct.productId, actionUuid)?.actionName || 'Action', @@ -167,7 +242,16 @@ function TriggerConnector() { }; if (firstSelectedPoint.actionUuid) { - addTrigger(firstSelectedPoint.actionUuid, trigger); + const event = addTrigger(selectedProduct.productId, firstSelectedPoint.actionUuid, trigger); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } } setFirstSelectedPoint(null); } @@ -184,7 +268,12 @@ function TriggerConnector() { pointUuid ); - if (!point) return; + const event = getEventByModelUuid(selectedProduct.productId, modelUuid); + + if (!point || !event) { + setFirstSelectedPoint(null); + return; + }; let actionUuid: string | undefined; if ('action' in point && point.action) { @@ -200,12 +289,12 @@ function TriggerConnector() { delay: 0, triggeredAsset: { triggeredModel: { - modelName: currentObject.parent?.parent?.name || 'Unknown', + modelName: event.modelName || 'Unknown', modelUuid: modelUuid }, triggeredPoint: { - pointName: currentObject.name, - pointUuid: pointUuid + pointName: 'Point', + pointUuid: point.uuid }, triggeredAction: actionUuid ? { actionName: getActionByUuid(selectedProduct.productId, actionUuid)?.actionName || 'Action', @@ -215,13 +304,24 @@ function TriggerConnector() { }; if (firstSelectedPoint.actionUuid) { - addTrigger(firstSelectedPoint.actionUuid, trigger); + const event = addTrigger(selectedProduct.productId, firstSelectedPoint.actionUuid, trigger); + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } } setFirstSelectedPoint(null); + } else if (firstSelectedPoint) { + setFirstSelectedPoint(null); } }; - if (subModule === 'simulations') { + if (subModule === 'simulations' && !deleteTool) { canvasElement.addEventListener("mousedown", onMouseDown); canvasElement.addEventListener("mouseup", onMouseUp); canvasElement.addEventListener("mousemove", onMouseMove); @@ -235,11 +335,149 @@ function TriggerConnector() { canvasElement.removeEventListener('contextmenu', handleRightClick); }; - }, [gl, subModule, selectedProduct, firstSelectedPoint]); + }, [gl, subModule, selectedProduct, firstSelectedPoint, deleteTool]); + + + useFrame(() => { + if (firstSelectedPoint) { + raycaster.setFromCamera(pointer, camera); + const intersects = raycaster.intersectObjects(scene.children, true).filter( + (intersect) => + !intersect.object.name.includes("Roof") && + !intersect.object.name.includes("agv-collider") && + !intersect.object.name.includes("MeasurementReference") && + !intersect.object.parent?.name.includes("Zone") && + !(intersect.object.type === "GridHelper") && + !(intersect.object.type === "Line2") + ); + + let point: THREE.Vector3 | null = null; + + if (intersects.length > 0) { + point = intersects[0].point; + if (point.y < 0.05) { + point = new THREE.Vector3(point.x, 0.05, point.z); + } + } + + const sphereIntersects = raycaster.intersectObjects(scene.children, true).filter((intersect) => intersect.object.name === ('Event-Sphere')); + + if (sphereIntersects.length > 0 && sphereIntersects[0].object.uuid === firstSelectedPoint.pointUuid) { + setHelperLineColor('red'); + setCurrentLine(null); + return; + } + + const startPoint = getWorldPositionFromScene(firstSelectedPoint.pointUuid); + + if (point && startPoint) { + if (sphereIntersects.length > 0) { + point = sphereIntersects[0].object.getWorldPosition(new THREE.Vector3()); + } + const distance = startPoint.distanceTo(point); + const heightFactor = Math.max(0.5, distance * 0.2); + const midPoint = new THREE.Vector3( + (startPoint.x + point.x) / 2, + Math.max(startPoint.y, point.y) + heightFactor, + (startPoint.z + point.z) / 2 + ); + + const endPoint = point; + + setCurrentLine({ + start: startPoint, + mid: midPoint, + end: endPoint, + }); + + setHelperLineColor(sphereIntersects.length > 0 ? "#6cf542" : "red"); + } else { + setCurrentLine(null); + } + } else { + setCurrentLine(null); + } + + }) + + const getWorldPositionFromScene = (pointUuid: string): THREE.Vector3 | null => { + const pointObj = scene.getObjectByProperty("uuid", pointUuid); + if (!pointObj) return null; + + const worldPosition = new THREE.Vector3(); + pointObj.getWorldPosition(worldPosition); + return worldPosition; + }; + + const removeConnection = (connection: ConnectionLine) => { + if (connection.trigger.triggerUuid) { + const event = removeTrigger(selectedProduct.productId, connection.trigger.triggerUuid); + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } + } + }; return ( - <> - + + {connections.map((connection) => { + const startPoint = getWorldPositionFromScene(connection.startPointUuid); + const endPoint = getWorldPositionFromScene(connection.endPointUuid); + + if (!startPoint || !endPoint) return null; + + const distance = startPoint.distanceTo(endPoint); + const heightFactor = Math.max(0.5, distance * 0.2); + const midPoint = new THREE.Vector3( + (startPoint.x + endPoint.x) / 2, + Math.max(startPoint.y, endPoint.y) + heightFactor, + (startPoint.z + endPoint.z) / 2 + ); + + return ( + (groupRefs.current[connection.id] = el!)} + start={startPoint.toArray()} + end={endPoint.toArray()} + mid={midPoint.toArray()} + color={deleteTool && hoveredLineKey === connection.id ? "red" : "#42a5f5"} + lineWidth={4} + dashed={deleteTool && hoveredLineKey === connection.id ? false : true} + dashSize={0.75} + dashScale={20} + onPointerOver={() => setHoveredLineKey(connection.id)} + onPointerOut={() => setHoveredLineKey(null)} + onClick={() => { + if (deleteTool) { + setHoveredLineKey(null); + setCurrentLine(null); + removeConnection(connection); + } + }} + userData={connection.trigger} + /> + ); + })} + + {currentLine && ( + + )} + ); } diff --git a/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx b/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx index 5dec724..2a124cd 100644 --- a/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx +++ b/app/src/modules/simulation/ui/vehicle/vehicleUI.tsx @@ -1,12 +1,16 @@ import React, { useEffect, useRef, useState } from 'react'; +import * as Types from "../../../../types/world/worldTypes"; import startPoint from "../../../../assets/gltf-glb/arrow_green.glb"; -import startEnd from "../../../../assets/gltf-glb/arrow_red.glb"; import * as THREE from "three"; +import startEnd from "../../../../assets/gltf-glb/arrow_red.glb"; import { useGLTF } from '@react-three/drei'; import { useFrame, useThree } from '@react-three/fiber'; -import { useSelectedEventSphere } from '../../../../store/simulation/useSimulationStore'; +import { useSelectedEventSphere, useIsDragging, useIsRotating } from '../../../../store/simulation/useSimulationStore'; import { useVehicleStore } from '../../../../store/simulation/useVehicleStore'; -import * as Types from "../../../../types/world/worldTypes"; +import { useProductStore } from '../../../../store/simulation/useProductStore'; +import { useSelectedProduct } from '../../../../store/simulation/useSimulationStore'; +import { upsertProductOrEventApi } from '../../../../services/simulation/UpsertProductOrEventApi'; + const VehicleUI = () => { const { scene: startScene } = useGLTF(startPoint) as any; const { scene: endScene } = useGLTF(startEnd) as any; @@ -14,67 +18,60 @@ const VehicleUI = () => { const endMarker = useRef(null); const prevMousePos = useRef({ x: 0, y: 0 }); const { selectedEventSphere } = useSelectedEventSphere(); - const { vehicles, updateVehicle } = useVehicleStore(); + const { selectedProduct } = useSelectedProduct(); + const { getVehicleById } = useVehicleStore(); + const { updateEvent } = useProductStore(); const [startPosition, setStartPosition] = useState<[number, number, number]>([0, 0, 0]); const [endPosition, setEndPosition] = useState<[number, number, number]>([0, 0, 0]); const [startRotation, setStartRotation] = useState<[number, number, number]>([0, 0, 0]); const [endRotation, setEndRotation] = useState<[number, number, number]>([0, 0, 0]); - const [isDragging, setIsDragging] = useState<"start" | "end" | null>(null); - const [isRotating, setIsRotating] = useState<"start" | "end" | null>(null); + const { isDragging, setIsDragging } = useIsDragging(); + const { isRotating, setIsRotating } = useIsRotating(); const { raycaster } = useThree(); const plane = useRef(new THREE.Plane(new THREE.Vector3(0, 1, 0), 0)); const state: Types.ThreeState = useThree(); const controls: any = state.controls; + const email = localStorage.getItem('email') + const organization = (email!.split("@")[1]).split(".")[0]; + + const updateBackend = ( + productName: string, + productId: string, + organization: string, + eventData: EventsSchema + ) => { + upsertProductOrEventApi({ + productName: productName, + productId: productId, + organization: organization, + eventDatas: eventData + }) + } + useEffect(() => { if (!selectedEventSphere) return; - const selectedVehicle = vehicles.find( - (vehicle: any) => vehicle.modelUuid === selectedEventSphere.userData.modelUuid - ); + const selectedVehicle = getVehicleById(selectedEventSphere.userData.modelUuid); if (selectedVehicle?.point?.action) { const { pickUpPoint, unLoadPoint } = selectedVehicle.point.action; if (pickUpPoint) { - const pickupPosition = new THREE.Vector3( - pickUpPoint.position.x, - pickUpPoint.position.y, - pickUpPoint.position.z - ); - const pickupRotation = new THREE.Vector3( - pickUpPoint.rotation.x, - pickUpPoint.rotation.y, - pickUpPoint.rotation.z - ); - pickupPosition.y = 0; - setStartPosition([pickupPosition.x, 0, pickupPosition.z]); - setStartRotation([pickupRotation.x, pickupRotation.y, pickupRotation.z]); + setStartPosition([pickUpPoint.position.x, 0, pickUpPoint.position.z]); + setStartRotation([pickUpPoint.rotation.x, pickUpPoint.rotation.y, pickUpPoint.rotation.z]); } else { const defaultLocal = new THREE.Vector3(0, 0, 1.5); const defaultWorld = selectedEventSphere.localToWorld(defaultLocal); - defaultWorld.y = 0; setStartPosition([defaultWorld.x, 0, defaultWorld.z]); setStartRotation([0, 0, 0]); } if (unLoadPoint) { - const unLoadPosition = new THREE.Vector3( - unLoadPoint.position.x, - unLoadPoint.position.y, - unLoadPoint.position.z - ); - const unLoadRotation = new THREE.Vector3( - unLoadPoint.rotation.x, - unLoadPoint.rotation.y, - unLoadPoint.position.z - ); - unLoadPosition.y = 0; // Force y to 0 - setEndPosition([unLoadPosition.x, 0, unLoadPosition.z]); - setEndRotation([unLoadRotation.x, unLoadRotation.y, unLoadRotation.z]); + setEndPosition([unLoadPoint.position.x, 0, unLoadPoint.position.z]); + setEndRotation([unLoadPoint.rotation.x, unLoadPoint.rotation.y, unLoadPoint.rotation.z]); } else { const defaultLocal = new THREE.Vector3(0, 0, -1.5); const defaultWorld = selectedEventSphere.localToWorld(defaultLocal); - defaultWorld.y = 0; // Force y to 0 setEndPosition([defaultWorld.x, 0, defaultWorld.z]); setEndRotation([0, 0, 0]); } @@ -87,7 +84,6 @@ const VehicleUI = () => { const intersects = raycaster.ray.intersectPlane(plane.current, intersectPoint); if (intersects) { - intersectPoint.y = 0; // Force y to 0 if (isDragging === "start") { setStartPosition([intersectPoint.x, 0, intersectPoint.z]); } @@ -108,12 +104,12 @@ const VehicleUI = () => { if (marker) { const rotationSpeed = 10; - marker.rotation.y += deltaX * rotationSpeed; if (isRotating === 'start') { - setStartRotation([marker.rotation.x, marker.rotation.y, marker.rotation.z]) + const y = startRotation[1] + deltaX * rotationSpeed; + setStartRotation([0, y, 0]); } else { - - setEndRotation([marker.rotation.x, marker.rotation.y, marker.rotation.z]) + const y = endRotation[1] + deltaX * rotationSpeed; + setEndRotation([0, y, 0]); } } }); @@ -142,43 +138,34 @@ const VehicleUI = () => { setIsRotating(null); if (selectedEventSphere?.userData.modelUuid) { - const updatedVehicle = vehicles.find( - (vehicle) => vehicle.modelUuid === selectedEventSphere.userData.modelUuid - ); + const updatedVehicle = getVehicleById(selectedEventSphere.userData.modelUuid); if (updatedVehicle) { - updateVehicle(selectedEventSphere.userData.modelUuid, { + const event = updateEvent(selectedProduct.productId, selectedEventSphere.userData.modelUuid, { point: { ...updatedVehicle.point, action: { ...updatedVehicle.point?.action, pickUpPoint: { - position: { - x: startPosition[0], - y: startPosition[1], - z: startPosition[2], - }, - rotation: { - x: startRotation[0], - y: startRotation[1], - z: startRotation[2], - }, + position: { x: startPosition[0], y: startPosition[1], z: startPosition[2], }, + rotation: { x: 0, y: startRotation[1], z: 0, }, }, unLoadPoint: { - position: { - x: endPosition[0], - y: endPosition[1], - z: endPosition[2], - }, - rotation: { - x: endRotation[0], - y: endRotation[1], - z: endRotation[2], - }, + position: { x: endPosition[0], y: endPosition[1], z: endPosition[2], }, + rotation: { x: 0, y: endRotation[1], z: 0, }, }, }, }, - }); + }) + + if (event) { + updateBackend( + selectedProduct.productName, + selectedProduct.productId, + organization, + event + ); + } } } }; @@ -208,6 +195,7 @@ const VehicleUI = () => { object={startScene} ref={startMarker} position={startPosition} + rotation={startRotation} onPointerDown={(e: any) => { e.stopPropagation(); handlePointerDown(e, "start", "start"); @@ -224,6 +212,7 @@ const VehicleUI = () => { object={endScene} ref={endMarker} position={endPosition} + rotation={endRotation} onPointerDown={(e: any) => { e.stopPropagation(); handlePointerDown(e, "end", "end"); diff --git a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx index 2f0b235..9ca7355 100644 --- a/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx +++ b/app/src/modules/simulation/vehicle/instances/animator/vehicleAnimator.tsx @@ -11,7 +11,7 @@ interface VehicleAnimatorProps { reset: () => void; currentPhase: string; agvUuid: number; - agvDetail: any; + agvDetail: VehicleStatus; } function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetail, reset }: VehicleAnimatorProps) { @@ -32,7 +32,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai let startTime: number; let fixedInterval: number; let coveredDistance = progressRef.current; - let objectRotation = (agvDetail.point?.action?.pickUpPoint?.rotation || { x: 0, y: 0, z: 0 }) as { x: number; y: number; z: number }; + let objectRotation = (agvDetail.point?.action?.pickUpPoint?.rotation || { x: 0, y: 0, z: 0 }) as { x: number; y: number; z: number } | undefined; useEffect(() => { @@ -67,10 +67,9 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai setRestingRotation(true); decrementVehicleLoad(agvDetail.modelUuid, 0); const object = scene.getObjectByProperty('uuid', agvUuid); - console.log('currentPhase: ', currentPhase); if (object) { object.position.set(agvDetail.position[0], agvDetail.position[1], agvDetail.position[2]); - object.rotation.set(objectRotation.x, objectRotation.y, objectRotation.z); + object.rotation.set(agvDetail.rotation[0], agvDetail.rotation[1], agvDetail.rotation[2]); } } }, [isReset, isPlaying]) @@ -132,7 +131,7 @@ function VehicleAnimator({ path, handleCallBack, currentPhase, agvUuid, agvDetai } if (progressRef.current >= totalDistance) { - if (restRotation) { + if (restRotation && objectRotation) { const targetQuaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(objectRotation.x, objectRotation.y, objectRotation.z)); object.quaternion.slerp(targetQuaternion, delta * 2); const angleDiff = object.quaternion.angleTo(targetQuaternion); diff --git a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx index 6a81d3a..412a4ba 100644 --- a/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx +++ b/app/src/modules/simulation/vehicle/instances/instance/vehicleInstance.tsx @@ -30,7 +30,8 @@ function VehicleInstance({ agvDetail }: any) { ); function vehicleStatus(modelId: string, status: string) { - // console.log(`${modelId} , ${status}); + // console.log(`${modelId} , ${status}`); + } // Function to reset everything @@ -44,7 +45,7 @@ function VehicleInstance({ agvDetail }: any) { const increment = () => { if (isIncrememtable.current) { - incrementVehicleLoad(agvDetail.modelUuid, 2); + incrementVehicleLoad(agvDetail.modelUuid, 10); isIncrememtable.current = false; } } @@ -69,6 +70,7 @@ function VehicleInstance({ agvDetail }: any) { increment(); }, 5000); + if (agvDetail.currentLoad === agvDetail.point.action.loadCapacity) { const toDrop = computePath( agvDetail.point.action.pickUpPoint.position, diff --git a/app/src/modules/simulation/vehicle/vehicles.tsx b/app/src/modules/simulation/vehicle/vehicles.tsx index 7badec5..19f049c 100644 --- a/app/src/modules/simulation/vehicle/vehicles.tsx +++ b/app/src/modules/simulation/vehicle/vehicles.tsx @@ -1,220 +1,48 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect } from "react"; import VehicleInstances from "./instances/vehicleInstances"; import { useVehicleStore } from "../../../store/simulation/useVehicleStore"; -import { useFloorItems } from "../../../store/store"; -import { useSelectedEventData, useSelectedEventSphere } from "../../../store/simulation/useSimulationStore"; +import { useSelectedEventData, useSelectedEventSphere, useSelectedProduct } from "../../../store/simulation/useSimulationStore"; import VehicleUI from "../ui/vehicle/vehicleUI"; import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; -function Vehicles() { +import { useProductStore } from "../../../store/simulation/useProductStore"; - const { vehicles, addVehicle } = useVehicleStore(); +function Vehicles() { + const { products, getProductById } = useProductStore(); + const { selectedProduct } = useSelectedProduct(); + const { vehicles, addVehicle, clearvehicles } = useVehicleStore(); const { selectedEventSphere } = useSelectedEventSphere(); const { selectedEventData } = useSelectedEventData(); - const { floorItems } = useFloorItems(); const { isPlaying } = usePlayButtonStore(); - const [vehicleStatusSample, setVehicleStatusSample] = useState< - VehicleEventSchema[] - >([ - { - modelUuid: "9356f710-4727-4b50-bdb2-9c1e747ecc74", - modelName: "AGV", - position: [97.9252965204558, 0, 37.96138815638661], - 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, - steeringAngle:0, - pickUpPoint: { position: { x: 98.71483985219794, y: 0, z: 28.66321267938962 }, rotation: { x: 0, y: 0, z: 0 } }, - unLoadPoint: { position: { x: 105.71483985219794, y: 0, z: 28.66321267938962 }, rotation: { x: 0, y: 0, z: 0 } }, - triggers: [ - { - triggerUuid: "trig-001", - triggerName: "Start Travel", - triggerType: "onComplete", - 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 - } - ] - } + useEffect(() => { + if (selectedProduct.productId) { + const product = getProductById(selectedProduct.productId); + if (product) { + clearvehicles(); + product.eventDatas.forEach(events => { + if (events.type === 'vehicle') { + addVehicle(selectedProduct.productId, events); + } + }); } - }, - { - 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, - steeringAngle:0, - pickUpPoint: null, - unLoadPoint: null, - 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: "cd7d0584-0684-42b4-b051-9e882c1914aa", - // modelName: "AGV", - // position: [105.90938758014703, 0, 31.584209911095215], - // 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, - // steeringAngle:0, - // pickUpPoint: null, - // unLoadPoint: null, - // 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", - // 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: 15, - // loadCapacity: 5, - // steeringAngle:0, - // pickUpPoint: null, - // unLoadPoint: null, - // 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 - // } - // ] - // } - // } - // } - ]); + } + }, [selectedProduct, products]); + useEffect(() => { // console.log('vehicles: ', vehicles); }, [vehicles]) - useEffect(() => { - addVehicle("123", vehicleStatusSample[0]); - addVehicle('123', vehicleStatusSample[1]); - // addVehicle('123', vehicleStatusSample[2]); - }, []); - - return ( <> + + {selectedEventSphere && selectedEventData?.data.type === "vehicle" && !isPlaying && < VehicleUI /> } + ); } -export default Vehicles; - - - +export default Vehicles; \ No newline at end of file diff --git a/app/src/modules/visualization/RealTimeVisulization.tsx b/app/src/modules/visualization/RealTimeVisulization.tsx index dadaf9c..a8f44b4 100644 --- a/app/src/modules/visualization/RealTimeVisulization.tsx +++ b/app/src/modules/visualization/RealTimeVisulization.tsx @@ -66,8 +66,7 @@ const RealTimeVisulization: React.FC = () => { const { selectedZone, setSelectedZone } = useSelectedZoneStore(); const { setRightSelect } = useRightSelected(); - const { editWidgetOptions, setEditWidgetOptions } = - useEditWidgetOptionsStore(); + const { editWidgetOptions, setEditWidgetOptions } = useEditWidgetOptionsStore(); const { rightClickSelected, setRightClickSelected } = useRightClickSelected(); const [openConfirmationPopup, setOpenConfirmationPopup] = useState(false); const { setFloatingWidget } = useFloatingWidget(); diff --git a/app/src/pages/Project.tsx b/app/src/pages/Project.tsx index c314a38..17a6315 100644 --- a/app/src/pages/Project.tsx +++ b/app/src/pages/Project.tsx @@ -28,9 +28,9 @@ import { createHandleDrop } from "../modules/visualization/functions/handleUiDro import { useSelectedZoneStore } from "../store/visualization/useZoneStore"; import { useFloatingWidget } from "../store/visualization/useDroppedObjectsStore"; import { useLogger } from "../components/ui/log/LoggerContext"; -import Footer from "../components/ui/footer/Footer"; import RenderOverlay from "../components/templates/Overlay"; import LogList from "../components/ui/log/LogList"; +import Footer from "../components/footer/Footer"; const Project: React.FC = () => { let navigate = useNavigate(); @@ -86,7 +86,7 @@ const Project: React.FC = () => { {!selectedUser && ( <> - {/* {loadingProgress > 0 && } */} + {loadingProgress > 0 && } {!isPlaying && ( <> {toggleThreeD && } @@ -122,7 +122,7 @@ const Project: React.FC = () => { } onDragOver={(event) => event.preventDefault()} > - {/* */} +
{selectedUser && } {isLogListVisible && ( diff --git a/app/src/store/simulation/useArmBotStore.ts b/app/src/store/simulation/useArmBotStore.ts index 642762f..3c54596 100644 --- a/app/src/store/simulation/useArmBotStore.ts +++ b/app/src/store/simulation/useArmBotStore.ts @@ -10,6 +10,7 @@ interface ArmBotStore { modelUuid: string, updates: Partial> ) => void; + clearArmBots: () => void; addCurrentAction: (modelUuid: string, actionUuid: string) => void; removeCurrentAction: (modelUuid: string) => void; @@ -39,14 +40,17 @@ export const useArmBotStore = create()( addArmBot: (productId, event) => { set((state) => { - state.armBots.push({ - ...event, - productId, - isActive: false, - idleTime: 0, - activeTime: 0, - state: 'idle', - }); + const exists = state.armBots.some(a => a.modelUuid === event.modelUuid); + if (!exists) { + state.armBots.push({ + ...event, + productId, + isActive: false, + idleTime: 0, + activeTime: 0, + state: 'idle', + }); + } }); }, @@ -65,6 +69,12 @@ export const useArmBotStore = create()( }); }, + clearArmBots: () => { + set((state) => { + state.armBots = []; + }); + }, + addCurrentAction: (modelUuid, actionUuid) => { set((state) => { const armBot = state.armBots.find(a => a.modelUuid === modelUuid); diff --git a/app/src/store/simulation/useConveyorStore.ts b/app/src/store/simulation/useConveyorStore.ts index 15dbf34..862ce79 100644 --- a/app/src/store/simulation/useConveyorStore.ts +++ b/app/src/store/simulation/useConveyorStore.ts @@ -10,6 +10,7 @@ interface ConveyorStore { modelUuid: string, updates: Partial> ) => void; + clearConveyors: () => void; setConveyorActive: (modelUuid: string, isActive: boolean) => void; setConveyorState: (modelUuid: string, newState: ConveyorStatus['state']) => void; @@ -30,14 +31,17 @@ export const useConveyorStore = create()( addConveyor: (productId, event) => { set((state) => { - state.conveyors.push({ - ...event, - productId, - isActive: false, - idleTime: 0, - activeTime: 0, - state: 'idle', - }); + const exists = state.conveyors.some(c => c.modelUuid === event.modelUuid); + if (!exists) { + state.conveyors.push({ + ...event, + productId, + isActive: false, + idleTime: 0, + activeTime: 0, + state: 'idle', + }); + } }); }, @@ -56,6 +60,12 @@ export const useConveyorStore = create()( }); }, + clearConveyors: () => { + set((state) => { + state.conveyors = []; + }); + }, + setConveyorActive: (modelUuid, isActive) => { set((state) => { const conveyor = state.conveyors.find(c => c.modelUuid === modelUuid); diff --git a/app/src/store/simulation/useEventsStore.ts b/app/src/store/simulation/useEventsStore.ts index 2d92fc2..5580eb1 100644 --- a/app/src/store/simulation/useEventsStore.ts +++ b/app/src/store/simulation/useEventsStore.ts @@ -7,7 +7,7 @@ type EventsStore = { // Event-level actions addEvent: (event: EventsSchema) => void; removeEvent: (modelUuid: string) => void; - updateEvent: (modelUuid: string, updates: Partial) => void; + updateEvent: (modelUuid: string, updates: Partial) => EventsSchema | undefined; // Point-level actions addPoint: (modelUuid: string, point: ConveyorPointSchema | VehiclePointSchema | RoboticArmPointSchema | MachinePointSchema | StoragePointSchema) => void; @@ -49,7 +49,9 @@ export const useEventsStore = create()( // Event-level actions addEvent: (event) => { set((state) => { - state.events.push(event); + if (!state.events.some(e => 'modelUuid' in e && e.modelUuid === event.modelUuid)) { + state.events.push(event); + } }); }, @@ -60,12 +62,15 @@ export const useEventsStore = create()( }, updateEvent: (modelUuid, updates) => { + let updatedEvent: EventsSchema | undefined; set((state) => { const event = state.events.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event) { Object.assign(event, updates); + updatedEvent = JSON.parse(JSON.stringify(event)); } }); + return updatedEvent; }, // Point-level actions @@ -73,9 +78,14 @@ export const useEventsStore = create()( set((state) => { const event = state.events.find(e => 'modelUuid' in e && e.modelUuid === modelUuid); if (event && 'points' in event) { - (event as ConveyorEventSchema).points.push(point as ConveyorPointSchema); + const existingPoint = (event as ConveyorEventSchema).points.find(p => p.uuid === point.uuid); + if (!existingPoint) { + (event as ConveyorEventSchema).points.push(point as ConveyorPointSchema); + } } else if (event && 'point' in event) { - (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any; + if (!(event as any).point || (event as any).point.uuid !== point.uuid) { + (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any; + } } }); }, @@ -110,14 +120,15 @@ export const useEventsStore = create()( const event = state.events.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) { + if (point && (!point.action || point.action.actionUuid !== action.actionUuid)) { point.action = action as any; } } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { - if ('action' in (event as any).point) { - (event as any).point.action = action; - } else if ('actions' in (event as any).point) { - (event as any).point.actions.push(action); + const point = (event as any).point; + if ('action' in point && (!point.action || point.action.actionUuid !== action.actionUuid)) { + point.action = action; + } else if ('actions' in point && !point.actions.some((a: any) => a.actionUuid === action.actionUuid)) { + point.actions.push(action); } } }); @@ -180,18 +191,22 @@ export const useEventsStore = create()( if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { if (point.action && point.action.actionUuid === actionUuid) { - point.action.triggers.push(trigger); + if (!point.action.triggers.some(t => t.triggerUuid === trigger.triggerUuid)) { + point.action.triggers.push(trigger); + } return; } } } else if ('point' in event) { - const point = (event as any).point; + const point: MachinePointSchema | VehiclePointSchema = (event as any).point; if ('action' in point && point.action.actionUuid === actionUuid) { - point.action.triggers.push(trigger); + if (!point.action.triggers.some(t => t.triggerUuid === trigger.triggerUuid)) { + point.action.triggers.push(trigger); + } return; } else if ('actions' in point) { - const action = point.actions.find((a: any) => a.actionUuid === actionUuid); - if (action) { + const action = (point as RoboticArmPointSchema).actions.find((a) => a.actionUuid === actionUuid); + if (action && !action.triggers.some(t => t.triggerUuid === trigger.triggerUuid)) { action.triggers.push(trigger); return; } diff --git a/app/src/store/simulation/useMachineStore.ts b/app/src/store/simulation/useMachineStore.ts index cc927f7..9a564ac 100644 --- a/app/src/store/simulation/useMachineStore.ts +++ b/app/src/store/simulation/useMachineStore.ts @@ -4,23 +4,23 @@ import { immer } from 'zustand/middleware/immer'; interface MachineStore { machines: MachineStatus[]; - // Actions addMachine: (productId: string, machine: MachineEventSchema) => void; removeMachine: (modelUuid: string) => void; updateMachine: ( modelUuid: string, updates: Partial> ) => void; + clearMachines: () => void; + + addCurrentAction: (modelUuid: string, actionUuid: string) => void; + removeCurrentAction: (modelUuid: string) => void; - // Status updates setMachineActive: (modelUuid: string, isActive: boolean) => void; setMachineState: (modelUuid: string, newState: MachineStatus['state']) => void; - // Time tracking incrementActiveTime: (modelUuid: string, incrementBy: number) => void; incrementIdleTime: (modelUuid: string, incrementBy: number) => void; - // Helpers getMachineById: (modelUuid: string) => MachineStatus | undefined; getMachinesByProduct: (productId: string) => MachineStatus[]; getMachinesBystate: (state: string) => MachineStatus[]; @@ -32,17 +32,19 @@ export const useMachineStore = create()( immer((set, get) => ({ machines: [], - // Actions addMachine: (productId, machine) => { set((state) => { - state.machines.push({ - ...machine, - productId, - isActive: false, - idleTime: 0, - activeTime: 0, - state: 'idle', - }); + const exists = state.machines.some(m => m.modelUuid === machine.modelUuid); + if (!exists) { + state.machines.push({ + ...machine, + productId, + isActive: false, + idleTime: 0, + activeTime: 0, + state: 'idle', + }); + } }); }, @@ -61,7 +63,36 @@ export const useMachineStore = create()( }); }, - // Status updates + clearMachines: () => { + set((state) => { + state.machines = []; + }); + }, + + addCurrentAction: (modelUuid) => { + set((state) => { + const armBot = state.machines.find(a => a.modelUuid === modelUuid); + if (armBot) { + const action = armBot.point.action; + if (action) { + armBot.currentAction = { + actionUuid: action.actionUuid, + actionName: action.actionName, + }; + } + } + }); + }, + + removeCurrentAction: (modelUuid) => { + set((state) => { + const armBot = state.machines.find(a => a.modelUuid === modelUuid); + if (armBot) { + armBot.currentAction = undefined; + } + }); + }, + setMachineActive: (modelUuid, isActive) => { set((state) => { const machine = state.machines.find(m => m.modelUuid === modelUuid); @@ -80,7 +111,6 @@ export const useMachineStore = create()( }); }, - // Time tracking incrementActiveTime: (modelUuid, incrementBy) => { set((state) => { const machine = state.machines.find(m => m.modelUuid === modelUuid); @@ -99,7 +129,6 @@ export const useMachineStore = create()( }); }, - // Helpers getMachineById: (modelUuid) => { return get().machines.find(m => m.modelUuid === modelUuid); }, diff --git a/app/src/store/simulation/useProductStore.ts b/app/src/store/simulation/useProductStore.ts index 4f2b546..b7448f2 100644 --- a/app/src/store/simulation/useProductStore.ts +++ b/app/src/store/simulation/useProductStore.ts @@ -33,27 +33,30 @@ type ProductsStore = { pointUuid: string, action: ConveyorPointSchema['action'] | VehiclePointSchema['action'] | RoboticArmPointSchema['actions'][0] | MachinePointSchema['action'] | StoragePointSchema['action'] ) => EventsSchema | undefined; - removeAction: (actionUuid: string) => EventsSchema | undefined; + removeAction: (productId: string, actionUuid: string) => EventsSchema | undefined; updateAction: ( + productId: string, actionUuid: string, updates: Partial ) => EventsSchema | undefined; // Trigger-level actions addTrigger: ( + productId: string, actionUuid: string, trigger: TriggerSchema - ) => void; - removeTrigger: (triggerUuid: string) => void; + ) => EventsSchema | undefined; + removeTrigger: (productId: string, triggerUuid: string) => EventsSchema | undefined; updateTrigger: ( + productId: string, triggerUuid: string, updates: Partial ) => void; // Renaming functions renameProduct: (productId: string, newName: string) => void; - renameAction: (actionUuid: string, newName: string) => EventsSchema | undefined; - renameTrigger: (triggerUuid: string, newName: string) => void; + renameAction: (productId: string, actionUuid: string, newName: string) => EventsSchema | undefined; + renameTrigger: (productId: string, triggerUuid: string, newName: string) => void; // Helper functions getProductById: (productId: string) => { productName: string; productId: string; eventDatas: EventsSchema[] } | undefined; @@ -71,12 +74,15 @@ export const useProductStore = create()( // Product-level actions addProduct: (productName, productId) => { set((state) => { - const newProduct = { - productName, - productId: productId, - eventDatas: [] - }; - state.products.push(newProduct); + const existingProduct = state.products.find(p => p.productId === productId); + if (!existingProduct) { + const newProduct = { + productName, + productId: productId, + eventDatas: [] + }; + state.products.push(newProduct); + } }); }, @@ -106,7 +112,10 @@ export const useProductStore = create()( set((state) => { const product = state.products.find(p => p.productId === productId); if (product) { - product.eventDatas.push(event); + const existingEvent = product.eventDatas.find(e => 'modelUuid' in e && e.modelUuid === event.modelUuid); + if (!existingEvent) { + product.eventDatas.push(event); + } } }); }, @@ -120,7 +129,7 @@ export const useProductStore = create()( }); }, - deleteEvent: (modelUuid: string) => { + deleteEvent: (modelUuid) => { set((state) => { for (const product of state.products) { product.eventDatas = product.eventDatas.filter(e => 'modelUuid' in e && e.modelUuid !== modelUuid); @@ -150,9 +159,15 @@ export const useProductStore = create()( if (product) { 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); + const existingPoint = (event as ConveyorEventSchema).points.find(p => p.uuid === point.uuid); + if (!existingPoint) { + (event as ConveyorEventSchema).points.push(point as ConveyorPointSchema); + } } else if (event && 'point' in event) { - (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any; + const existingPoint = (event as any).point?.uuid === point.uuid; + if (!existingPoint) { + (event as VehicleEventSchema | RoboticArmEventSchema | MachineEventSchema | StorageEventSchema).point = point as any; + } } } }); @@ -198,17 +213,22 @@ export const useProductStore = create()( 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) { + if (point && (!point.action || point.action.actionUuid !== action.actionUuid)) { point.action = action as any; updatedEvent = JSON.parse(JSON.stringify(event)); } } else if (event && 'point' in event && (event as any).point.uuid === pointUuid) { if ('action' in (event as any).point) { - (event as any).point.action = action; - updatedEvent = JSON.parse(JSON.stringify(event)); + if (!(event as any).point.action || (event as any).point.action.actionUuid !== action.actionUuid) { + (event as any).point.action = action; + updatedEvent = JSON.parse(JSON.stringify(event)); + } } else if ('actions' in (event as any).point) { - (event as any).point.actions.push(action); - updatedEvent = JSON.parse(JSON.stringify(event)); + const existingAction = (event as any).point.actions.find((a: any) => a.actionUuid === action.actionUuid); + if (!existingAction) { + (event as any).point.actions.push(action); + updatedEvent = JSON.parse(JSON.stringify(event)); + } } } } @@ -216,10 +236,11 @@ export const useProductStore = create()( return updatedEvent; }, - removeAction: (actionUuid: string) => { + removeAction: (productId, actionUuid) => { let updatedEvent: EventsSchema | undefined; set((state) => { - for (const product of state.products) { + const product = state.products.find(p => p.productId === productId); + if (product) { for (const event of product.eventDatas) { if ('points' in event) { // Handle ConveyorEventSchema @@ -248,10 +269,11 @@ export const useProductStore = create()( return updatedEvent; }, - updateAction: (actionUuid, updates) => { + updateAction: (productId, actionUuid, updates) => { let updatedEvent: EventsSchema | undefined; set((state) => { - for (const product of state.products) { + const product = state.products.find(p => p.productId === productId); + if (product) { for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { @@ -283,26 +305,40 @@ export const useProductStore = create()( }, // Trigger-level actions - addTrigger: (actionUuid, trigger) => { + addTrigger: (productId, actionUuid, trigger) => { + let updatedEvent: EventsSchema | undefined; set((state) => { - for (const product of state.products) { + const product = state.products.find(p => p.productId === productId); + if (product) { 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.triggers.push(trigger); + const existingTrigger = point.action.triggers.find(t => t.triggerUuid === trigger.triggerUuid); + if (!existingTrigger) { + point.action.triggers.push(trigger); + updatedEvent = JSON.parse(JSON.stringify(event)); + } return; } } } else if ('point' in event) { const point = (event as any).point; if ('action' in point && point.action.actionUuid === actionUuid) { - point.action.triggers.push(trigger); + const existingTrigger = point.action.triggers.find((t: any) => t.triggerUuid === trigger.triggerUuid); + if (!existingTrigger) { + point.action.triggers.push(trigger); + updatedEvent = JSON.parse(JSON.stringify(event)); + } return; } else if ('actions' in point) { const action = point.actions.find((a: any) => a.actionUuid === actionUuid); if (action) { - action.triggers.push(trigger); + const existingTrigger = action.triggers.find((t: any) => t.triggerUuid === trigger.triggerUuid); + if (!existingTrigger) { + action.triggers.push(trigger); + updatedEvent = JSON.parse(JSON.stringify(event)); + } return; } } @@ -310,26 +346,41 @@ export const useProductStore = create()( } } }); + return updatedEvent; }, - removeTrigger: (triggerUuid) => { + removeTrigger: (productId, triggerUuid) => { + let updatedEvent: EventsSchema | undefined; set((state) => { - for (const product of state.products) { + const product = state.products.find(p => p.productId === productId); + if (product) { 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) { - point.action.triggers = point.action.triggers.filter(t => t.triggerUuid !== triggerUuid); + const Trigger = point.action.triggers.find(t => t.triggerUuid === triggerUuid); + if (Trigger) { + point.action.triggers = point.action.triggers.filter(t => t.triggerUuid !== triggerUuid); + updatedEvent = JSON.parse(JSON.stringify(event)); + } } } } else if ('point' in event) { const point = (event as any).point; if ('action' in point && 'triggers' in point.action) { - point.action.triggers = point.action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid); + const Trigger = point.action.triggers.find((t: any) => t.triggerUuid === triggerUuid); + if (Trigger) { + point.action.triggers = point.action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid); + updatedEvent = JSON.parse(JSON.stringify(event)); + } } else if ('actions' in point) { for (const action of point.actions) { if ('triggers' in action) { - action.triggers = action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid); + const Trigger = action.triggers.find((t: any) => t.triggerUuid === triggerUuid); + if (Trigger) { + action.triggers = action.triggers.filter((t: any) => t.triggerUuid !== triggerUuid); + updatedEvent = JSON.parse(JSON.stringify(event)); + } } } } @@ -337,11 +388,13 @@ export const useProductStore = create()( } } }); + return updatedEvent; }, - updateTrigger: (triggerUuid, updates) => { + updateTrigger: (productId, triggerUuid, updates) => { set((state) => { - for (const product of state.products) { + const product = state.products.find(p => p.productId === productId); + if (product) { for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { @@ -388,10 +441,11 @@ export const useProductStore = create()( }); }, - renameAction: (actionUuid, newName) => { + renameAction: (productId, actionUuid, newName) => { let updatedEvent: EventsSchema | undefined; set((state) => { - for (const product of state.products) { + const product = state.products.find(p => p.productId === productId); + if (product) { for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { @@ -422,9 +476,10 @@ export const useProductStore = create()( return updatedEvent; }, - renameTrigger: (triggerUuid, newName) => { + renameTrigger: (productId, triggerUuid, newName) => { set((state) => { - for (const product of state.products) { + const product = state.products.find(p => p.productId === productId); + if (product) { for (const event of product.eventDatas) { if ('points' in event) { for (const point of (event as ConveyorEventSchema).points) { diff --git a/app/src/store/simulation/useSimulationStore.ts b/app/src/store/simulation/useSimulationStore.ts index 5085688..6e4321f 100644 --- a/app/src/store/simulation/useSimulationStore.ts +++ b/app/src/store/simulation/useSimulationStore.ts @@ -114,4 +114,36 @@ export const useSelectedAction = create()( }); }, })) +); + +interface IsDraggingState { + isDragging: "start" | "end" | null; + setIsDragging: (state: "start" | "end" | null) => void; +} + +export const useIsDragging = create()( + immer((set) => ({ + isDragging: null, + setIsDragging: (state) => { + set((s) => { + s.isDragging = state; + }); + }, + })) +); + +interface IsRotatingState { + isRotating: "start" | "end" | null; + setIsRotating: (state: "start" | "end" | null) => void; +} + +export const useIsRotating = create()( + immer((set) => ({ + isRotating: null, + setIsRotating: (state) => { + set((s) => { + s.isRotating = state; + }); + }, + })) ); \ No newline at end of file diff --git a/app/src/store/simulation/useStorageUnitStore.ts b/app/src/store/simulation/useStorageUnitStore.ts index d729708..aec2f12 100644 --- a/app/src/store/simulation/useStorageUnitStore.ts +++ b/app/src/store/simulation/useStorageUnitStore.ts @@ -4,26 +4,22 @@ import { immer } from 'zustand/middleware/immer'; interface StorageUnitStore { storageUnits: StorageUnitStatus[]; - // Actions addStorageUnit: (productId: string, storageUnit: StorageEventSchema) => void; removeStorageUnit: (modelUuid: string) => void; updateStorageUnit: ( modelUuid: string, updates: Partial> ) => void; + clearStorageUnits: () => void; - // Status updates setStorageUnitActive: (modelUuid: string, isActive: boolean) => void; setStorageUnitState: (modelUuid: string, newState: StorageUnitStatus['state']) => void; - // Load updates updateStorageUnitLoad: (modelUuid: string, incrementBy: number) => void; - // Time tracking incrementActiveTime: (modelUuid: string, incrementBy: number) => void; incrementIdleTime: (modelUuid: string, incrementBy: number) => void; - // Helpers getStorageUnitById: (modelUuid: string) => StorageUnitStatus | undefined; getStorageUnitsByProduct: (productId: string) => StorageUnitStatus[]; getStorageUnitsBystate: (state: string) => StorageUnitStatus[]; @@ -37,18 +33,20 @@ export const useStorageUnitStore = create()( immer((set, get) => ({ storageUnits: [], - // Actions addStorageUnit: (productId, storageUnit) => { set((state) => { - state.storageUnits.push({ - ...storageUnit, - productId, - isActive: false, - idleTime: 0, - activeTime: 0, - currentLoad: 0, - state: 'idle', - }); + const exists = state.storageUnits.some(s => s.modelUuid === storageUnit.modelUuid); + if (!exists) { + state.storageUnits.push({ + ...storageUnit, + productId, + isActive: false, + idleTime: 0, + activeTime: 0, + currentLoad: 0, + state: 'idle', + }); + } }); }, @@ -67,7 +65,12 @@ export const useStorageUnitStore = create()( }); }, - // Status updates + clearStorageUnits: () => { + set(() => ({ + storageUnits: [], + })); + }, + setStorageUnitActive: (modelUuid, isActive) => { set((state) => { const unit = state.storageUnits.find(s => s.modelUuid === modelUuid); @@ -86,7 +89,6 @@ export const useStorageUnitStore = create()( }); }, - // Load updates updateStorageUnitLoad: (modelUuid, incrementBy) => { set((state) => { const unit = state.storageUnits.find(s => s.modelUuid === modelUuid); @@ -96,7 +98,6 @@ export const useStorageUnitStore = create()( }); }, - // Time tracking incrementActiveTime: (modelUuid, incrementBy) => { set((state) => { const unit = state.storageUnits.find(s => s.modelUuid === modelUuid); @@ -115,7 +116,6 @@ export const useStorageUnitStore = create()( }); }, - // Helpers getStorageUnitById: (modelUuid) => { return get().storageUnits.find(s => s.modelUuid === modelUuid); }, diff --git a/app/src/store/simulation/useVehicleStore.ts b/app/src/store/simulation/useVehicleStore.ts index a5ea3be..ecef809 100644 --- a/app/src/store/simulation/useVehicleStore.ts +++ b/app/src/store/simulation/useVehicleStore.ts @@ -20,6 +20,7 @@ interface VehiclesStore { modelUuid: string, updates: Partial> ) => void; + clearvehicles: () => void; setVehicleActive: (modelUuid: string, isActive: boolean) => void; updateSteeringAngle: (modelUuid: string, steeringAngle: number) => void; @@ -41,15 +42,18 @@ export const useVehicleStore = create()( addVehicle: (productId, event) => { set((state) => { - state.vehicles.push({ - ...event, - productId, - isActive: false, - idleTime: 0, - activeTime: 0, - currentLoad: 0, - distanceTraveled: 0, - }); + const exists = state.vehicles.some(v => v.modelUuid === event.modelUuid); + if (!exists) { + state.vehicles.push({ + ...event, + productId, + isActive: false, + idleTime: 0, + activeTime: 0, + currentLoad: 0, + distanceTraveled: 0, + }); + } }); }, @@ -68,6 +72,12 @@ export const useVehicleStore = create()( }); }, + clearvehicles: () => { + set((state) => { + state.vehicles = []; + }); + }, + setVehicleActive: (modelUuid, isActive) => { set((state) => { const vehicle = state.vehicles.find(v => v.modelUuid === modelUuid); diff --git a/app/src/styles/abstracts/variables.scss b/app/src/styles/abstracts/variables.scss index f4a6495..29976dc 100644 --- a/app/src/styles/abstracts/variables.scss +++ b/app/src/styles/abstracts/variables.scss @@ -22,7 +22,11 @@ $text-button-color-dark: #f3f3fd; // background colors // ---------- light mode ---------- $background-color: linear-gradient(-45deg, #fcfdfd71 0%, #fcfdfd79 100%); -$background-color-solid-gradient: linear-gradient(-45deg, #fcfdfd 0%, #fcfdfd 100%); +$background-color-solid-gradient: linear-gradient( + -45deg, + #fcfdfd 0%, + #fcfdfd 100% +); $background-color-solid: #fcfdfd; $background-color-secondary: #fcfdfd4d; $background-color-accent: #6f42c1; @@ -45,7 +49,11 @@ $background-radial-gray-gradient: radial-gradient( // ---------- dark mode ---------- $background-color-dark: linear-gradient(-45deg, #333333b3 0%, #2d2437b3 100%); -$background-color-solid-gradient-dark: linear-gradient(-45deg, #333333 0%, #2d2437 100%); +$background-color-solid-gradient-dark: linear-gradient( + -45deg, + #333333 0%, + #2d2437 100% +); $background-color-solid-dark: #19191d; $background-color-secondary-dark: #19191d99; $background-color-accent-dark: #6f42c1; @@ -104,6 +112,21 @@ $color3: #b186ff; $color4: #8752e8; $color5: #c7a8ff; +// log indication colors +// ------------ text ------------- +$log-default-text-color: #6f42c1; +$log-info-text-color: #488ef6; +$log-warn-text-color: #f3a50c; +$log-error-text-color: #f65648; +$log-success-text-color: #43c06d; + +// ------------ background ------------- +$log-default-backgroung-color: #6e42c133; +$log-info-background-color: #488ef633; +$log-warn-background-color: #f3a50c33; +$log-error-background-color: #f6564833; +$log-success-background-color: #43c06d33; + // old variables $accent-color: #6f42c1; $accent-color-dark: #c4abf1; diff --git a/app/src/styles/base/base.scss b/app/src/styles/base/base.scss index bf53164..f4a260f 100644 --- a/app/src/styles/base/base.scss +++ b/app/src/styles/base/base.scss @@ -37,11 +37,9 @@ // old colors --accent-color: #{$accent-color}; - --highlight-accent-color: #{$highlight-accent-color}; --accent-gradient-color: #{$acent-gradient}; --faint-gradient-color: #{$faint-gradient}; --background-color-gray: #{$background-color-gray}; - --border-color: #{$border-color}; --shadow-main-light: #{$shadow-color}; --box-shadow-light: 0px 2px 4px var(--shadow-main-light); --box-shadow-medium: 0px 4px 8px var(--shadow-main-light); @@ -75,7 +73,7 @@ --background-radial-gray-gradient: #{$background-radial-gray-gradient-dark}; // border colors - --border-color: #{$border-color}; + --border-color: #{$border-color-dark}; --input-border-color: #{$input-border-color-dark}; --border-color-accent: #{$border-color-accent-dark}; @@ -89,11 +87,9 @@ // old colors --accent-color: #{$accent-color-dark}; - --highlight-accent-color: #{$highlight-accent-color-dark}; --accent-gradient-color: #{$acent-gradient-dark}; --faint-gradient-color: #{$faint-gradient-dark}; --background-color-gray: #{$background-color-gray-dark}; - --border-color: #{$border-color-dark}; --shadow-main-dark: #{$shadow-color}; --box-shadow-light: 0px 2px 4px var(--shadow-main-dark); --box-shadow-medium: 0px 4px 8px var(--shadow-main-dark); @@ -137,6 +133,23 @@ body { --color3: #{$color3}; --color4: #{$color4}; --color5: #{$color5}; + + + + + --default-text-color: #{$log-default-text-color}; + --log-info-text-color: #{$log-info-text-color}; + --log-warn-text-color: #{$log-warn-text-color}; + --log-error-text-color: #{$log-error-text-color}; + --log-success-text-color: #{$log-success-text-color}; + + // ------------ background ------------- + --log-default-background-color: #{$log-default-backgroung-color}; + --log-info-background-color: #{$log-info-background-color}; + --log-warn-background-color: #{$log-warn-background-color}; + --log-error-background-color: #{$log-error-background-color}; + --log-success-background-color: #{$log-success-background-color}; + } ::-webkit-scrollbar { @@ -160,4 +173,4 @@ body { ::-webkit-scrollbar-corner { background: transparent; -} +} \ No newline at end of file diff --git a/app/src/styles/base/global.scss b/app/src/styles/base/global.scss index f86ba0f..f13344e 100644 --- a/app/src/styles/base/global.scss +++ b/app/src/styles/base/global.scss @@ -22,6 +22,7 @@ section, transform: translate(0, -50%); transition: all 0.2s; box-shadow: $box-shadow-medium; + background: var(--background-color-solid); canvas { outline: none; border: none; diff --git a/app/src/styles/components/analysis/ROISummary.scss b/app/src/styles/components/analysis/ROISummary.scss deleted file mode 100644 index 5e2f78a..0000000 --- a/app/src/styles/components/analysis/ROISummary.scss +++ /dev/null @@ -1,311 +0,0 @@ -@use "../../abstracts/variables" as *; -@use "../../abstracts/mixins" as *; - -.roiSummary-container { - .roiSummary-wrapper { - background-color: var(--background-color); - - .product-info { - display: flex; - } - - .playBack { - display: flex; - background-color: var(--background-color); - border-radius: 12px; - padding: 6px; - - .info { - span { - font-size: var(--font-size-xlarge); - - &:first-child { - color: #31C756; - } - - &:last-child { - color: var(--text-color); - } - } - } - } - - .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: 100%; - } - - .metrics { - display: flex; - flex-direction: column; - gap: 6px; - - .metric-item { - width: 100%; - border-radius: 6px; - border: 1px solid #00FF56; - background: #17eb5d65; - display: flex; - flex-direction: column; - padding: 4px 6px; - - &:last-child { - align-items: center; - } - - .metric-label { - font-size: 10px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - .metric-value { - text-align: center; - line-height: 20px; - } - } - - .metric-wrapper { - display: flex; - gap: 6px; - - .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); - border-radius: 8px; - padding: 16px; - - .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; - } - - .section-title { - font-size: var(--font-size-regular); - color: var(--text-color); - } - - .expand-icon { - font-size: 16px; - 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; - border-radius: 8px; - - th, - td { - padding: 8px; - text-align: left; - border-top: 1px solid var(--text-disabled); - border-bottom: 1px solid var(--text-disabled); - } - - th:first-child, - td:first-child { - border-left: 1px solid var(--text-disabled); - } - - th:last-child, - td:last-child { - border-right: 1px solid var(--text-disabled); - } - - th { - background-color: var(--background-color); - color: #333; - } - - .total-row, - .net-profit-row { - font-weight: bold; - color: #333; - } - } - } - - .tips-section { - background-color: var(--background-color); - border-radius: 8px; - display: flex; - flex-direction: column; - gap: 6px; - padding: 12px; - - .tip-header { - display: flex; - align-items: center; - - .tip-title { - color: var(--text-color); - font-weight: 600; - } - } - - .tip-description { - span { - font-size: var(--font-size-xlarge); - color: #34C759; - - &:first-child { - color: #34C759; - } - - &:nth-child(2) { - color: #488EF6; - } - } - } - } - - .get-tips-button { - border: none; - border-radius: 5px; - cursor: pointer; - font-size: 14px; - margin-top: 8px; - display: inline-block; - display: flex; - justify-content: flex-end; - background: none; - - .btn { - color: var(--text-button-color); - background: var(--background-color-button); - padding: 4px 12px; - border-radius: #{$border-radius-large}; - display: inline-block; - text-align: center; - } - } - } - - .semi-circle-wrapper { - width: 100%; - height: 125px; - overflow-y: hidden; - position: relative; - .semi-circle { - width: 100%; - height: 250px; - border-radius: 50%; - position: relative; - } - .progress-cover { - position: absolute; - width: 75%; - height: 75%; - top: 12.5%; - left: 12.5%; - 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 - -.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; - } - - - - } -} \ No newline at end of file diff --git a/app/src/styles/components/footer/footer.scss b/app/src/styles/components/footer/footer.scss index 0fed13b..ba869a4 100644 --- a/app/src/styles/components/footer/footer.scss +++ b/app/src/styles/components/footer/footer.scss @@ -1,57 +1,122 @@ +@use "../../abstracts/variables" as *; +@use "../../abstracts/mixins" as *; + .footer-wrapper { - position: absolute; - bottom: 0; - left: 0; - width: 100%; - z-index: 1; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + z-index: 1; + display: flex; + justify-content: space-between; + padding: 2px 12px; + + .selection-wrapper { display: flex; - justify-content: space-between; - padding: 12px 24px; + gap: 6px; - .selection-wrapper { - display: flex; - gap: 6px; + .selector-wrapper { + display: flex; + gap: 6px; + align-items: center; + background: var(--background-color); + padding: 3px 6px; + border-radius: 12px; + color: var(--text-color); - .selector-wrapper { - display: flex; - gap: 6px; - align-items: center; - background: var(--background-color); - padding: 3px 6px; - border-radius: 12px; - color: var(--text-color); + .selector { + color: var(--text-color); + } + } + } - .selector { - color: var(--text-color); - } - } + .logs-wrapper { + display: flex; + gap: 6px; + + .logs-detail, + .version { + border-radius: 12px; + background: var(--background-color); + padding: 3px 6px; + color: var(--text-color); + display: flex; + align-items: center; + gap: 6px; } - .logs-wrapper { - display: flex; - gap: 6px; + .log { + background-color: var(--log-default-background-color); - .logs-detail, - .version { + .log-message { + color: var(--default-text-color); - border-radius: 12px; - background: var(--background-color); - padding: 3px 6px; - color: var(--text-color); - display: flex; - align-items: center; - gap: 6px; - } - - .logs-detail { - // background-color: #fff; - cursor: pointer; - } - - .version { - display: flex; - gap: 6px; - - } + } } + + .info { + background-color: var(--log-info-background-color); + + .log-message { + color: var(--log-info-text-color); + + } + } + + .error { + background-color: var(--log-error-background-color); + + .log-message { + color: var(--log-error-text-color); + + } + } + + .warning { + background-color: var(--log-warn-background-color); + + .log-message { + color: var(--log-warn-text-color); + + } + } + + .success { + background-color: var(--log-success-background-color); + + .log-message { + color: var(--log-success-text-color); + + } + } + + + + + .logs-detail { + padding: 2px 12px; + cursor: pointer; + + .log-icon { + @include flex-center; + } + + .log-message { + max-width: 40vw; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + + .version { + font-size: var(--font-size-tiny); + display: flex; + gap: 6px; + + .icon { + @include flex-center; + } + } + } } \ No newline at end of file diff --git a/app/src/styles/components/layouts.scss b/app/src/styles/components/layouts.scss deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/styles/components/logs/logs.scss b/app/src/styles/components/logs/logs.scss index ba0f43d..f07708b 100644 --- a/app/src/styles/components/logs/logs.scss +++ b/app/src/styles/components/logs/logs.scss @@ -1,77 +1,105 @@ +@use "../../abstracts/variables" as *; +@use "../../abstracts/mixins" as *; + .log-list-container { - width: 100vw; - height: 100vh; - // background: var(--background-color-secondary); - // backdrop-filter: blur(2px); + width: 100vw; + height: 100vh; + background: var(--background-color-secondary); - .log-list-wrapper { - height: 50%; - min-width: 50%; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - z-index: 5; - background: var(--background-color); - padding: 14px 12px; - border-radius: 15px; + .log-list-wrapper { + height: 50%; + min-width: 50%; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 5; + background: var(--background-color); + padding: 14px 12px; + border-radius: 15px; + display: flex; + flex-direction: column; + gap: 12px; + backdrop-filter: blur(50px); + outline: 1px solid var(--border-color); + + .log-header { + display: flex; + justify-content: space-between; + + .log-header-wrapper { display: flex; - flex-direction: column; - gap: 12px; - backdrop-filter: blur(50px); + align-items: center; + gap: 6px; + } - .log-header { - display: flex; - justify-content: space-between; - - .log-header-wrapper { - display: flex; - align-items: center; - gap: 6px; - } - - .close { - // transform: scale(1.5); - cursor: pointer; - } - } - - .log-nav-wrapper { - display: flex; - gap: 6px; - - .log-nav { - padding: 8px 16px; - border-radius: 19px; - } - - .log-nav.active { - background-color: var(--accent-color); - color: var(--text-button-color); - } - } - - .log-entry-wrapper { - height: 100%; - display: flex; - flex-direction: column; - gap: 4px; - background: var(--background-color); - padding: 18px 10px; - border-radius: 16px; - - .log-entry { - padding: 4px; - border-radius: 4px; - font-size: var(--font-size-small); - display: flex; - align-items: center; - gap: 6px; - - &:nth-child(odd) { - background: var(--background-color); - } - } + .close { + @include flex-center; + height: 28px; + width: 28px; + cursor: pointer; + svg { + scale: 1.6; } + } } -} \ No newline at end of file + + .log-nav-wrapper { + display: flex; + gap: 6px; + + .log-nav { + padding: 8px 16px; + border-radius: 19px; + } + + .log-nav.active { + background-color: var(--background-color-accent); + color: var(--text-button-color); + } + } + + .log-entry-wrapper { + height: 100%; + display: flex; + flex-direction: column; + gap: 4px; + background: var(--background-color); + padding: 18px 10px; + border-radius: 16px; + outline: 1px solid var(--border-color); + outline-offset: -1px; + + .log-entry { + padding: 4px; + border-radius: 4px; + font-size: var(--font-size-small); + display: flex; + align-items: center; + gap: 6px; + + .log-icon { + @include flex-center; + } + .log-entry-message-container { + @include flex-space-between; + gap: 12px; + width: 100%; + .message-time { + font-size: var(--font-size-tiny); + font-weight: 300; + opacity: 0.8; + text-wrap: nowrap; + } + .log-entry-message{ + width: 100%; + } + } + + &:nth-child(odd) { + background: var(--background-color); + } + } + } + } +} diff --git a/app/src/styles/components/analysis/analysis.scss b/app/src/styles/components/simulation/analysis.scss similarity index 50% rename from app/src/styles/components/analysis/analysis.scss rename to app/src/styles/components/simulation/analysis.scss index e71a25f..f33fb46 100644 --- a/app/src/styles/components/analysis/analysis.scss +++ b/app/src/styles/components/simulation/analysis.scss @@ -1,3 +1,6 @@ +@use "../../abstracts/variables" as *; +@use "../../abstracts/mixins" as *; + .analysis { position: fixed; top: 0; @@ -19,12 +22,8 @@ .analysis-card { min-width: 333px; - background: var(--background-color); border-radius: 20px; padding: 8px; - backdrop-filter: blur(10px); - outline: 1px solid var(--border-color); - outline-offset: -1px; pointer-events: all; .analysis-card-wrapper { @@ -35,6 +34,9 @@ display: flex; flex-direction: column; gap: 14px; + backdrop-filter: blur(10px); + outline: 1px solid var(--border-color); + outline-offset: -1px; .card-header { width: 100%; @@ -43,13 +45,13 @@ align-items: center; .main-header { - color: var(--text-color); + line-height: 20px; font-size: var(--font-size-regular); } .sub-header { - color: var(--input-text-color); font-size: var(--font-size-tiny); + color: var(--text-button-color); } } @@ -173,7 +175,7 @@ padding: 8px; display: flex; flex-direction: column; - gap: 16px; + gap: 12px; &:first-child { width: 85%; @@ -189,21 +191,6 @@ align-items: center; justify-content: end; gap: 6px; - - .progress-indicator { - padding-top: 10px; - } - - .value-wrapper { - .value { - font-size: var(--font-size-xlarge); - } - - .unit { - font-size: var(--font-size-small); - - } - } } } @@ -245,7 +232,7 @@ justify-content: space-between; width: 100%; gap: 6px; - + padding-top: 3px; .shift-wrapper { display: flex; align-items: center; @@ -288,4 +275,276 @@ } } } -} \ No newline at end of file + + .roiSummary-wrapper { + max-width: 470px; + background-color: var(--background-color); + + .product-info { + display: flex; + align-items: center; + gap: 6px; + } + + .playBack { + display: flex; + align-items: center; + gap: 2px; + border-radius: 66px; + background: var(--background-color); + padding: 6px 12px; + border: 1px solid var(--border-color); + + .info { + span { + font-size: var(--font-size-xlarge); + + &:first-child { + color: #31c756; + } + + &:last-child { + color: var(--text-color); + } + } + } + } + + .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: #28B9F3; + } + } + } + + .roi-progress { + width: 100%; + } + + .metrics { + display: flex; + flex-direction: column; + gap: 6px; + + .metric-item { + width: 100%; + border-radius: #{$border-radius-xxx}; + border: 1px solid #00ff56; + background: #17eb5e42; + display: flex; + flex-direction: column; + padding: 4px 8px; + + &:last-child { + align-items: center; + } + + .metric-label { + opacity: 0.8; + font-size: 10px; + font-weight: 300; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .metric-value { + text-align: center; + line-height: 20px; + } + } + + .metric-wrapper { + display: flex; + gap: 6px; + + .metric-item { + border-radius: #{$border-radius-large}; + background-color: var(--background-color); + border: 1px solid var(--border-color); + } + } + } + } + + .cost-breakdown { + background-color: var(--background-color); + border: 1px solid var(--border-color); + border-radius: #{$border-radius-extra-large}; + padding: 16px; + + .breakdown-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + + .section-wrapper { + display: flex; + gap: 4px; + align-items: center; + } + + .section-number { + color: #00aaff; + } + + .section-title { + font-size: var(--font-size-regular); + color: var(--text-color); + } + + .expand-icon { + font-size: 16px; + 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; + border-radius: 8px; + overflow: hidden; + outline: 1px solid var(--border-color); + outline-offset: -1px; + margin-top: 12px; + + th, + td { + color: var(--text-color); + padding: 8px; + text-align: left; + border: 1px solid var(--border-color); + } + + th { + background-color: var(--background-color); + } + } + } + + .tips-section { + background-color: var(--background-color); + border-radius: #{$border-radius-large}; + outline: 1px solid var(--border-color); + display: flex; + flex-direction: column; + gap: 6px; + padding: 12px; + + .tip-header { + display: flex; + align-items: center; + + .tip-title { + color: var(--text-color); + font-weight: 600; + } + } + + .tip-description { + span { + font-size: var(--font-size-xlarge); + color: #34c759; + + &:first-child { + color: #34c759; + } + + &:nth-child(2) { + color: #488ef6; + } + } + } + } + + .get-tips-button { + border: none; + border-radius: 5px; + cursor: pointer; + font-size: 14px; + margin-top: 8px; + display: inline-block; + display: flex; + justify-content: flex-end; + background: none; + + .btn { + color: var(--text-button-color); + background: var(--background-color-button); + padding: 4px 12px; + border-radius: #{$border-radius-large}; + display: inline-block; + text-align: center; + } + } + } + + .svg-half-donut { + position: relative; + + .label-wrapper { + width: 100%; + position: absolute; + bottom: 0px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + + .label { + font-size: var(--font-size-xlarge); + } + } + } + +} + +.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; + } + } +} + +// Breakdown Table Open/Close Logic \ No newline at end of file diff --git a/app/src/styles/main.scss b/app/src/styles/main.scss index 9fe7222..3adfc0f 100644 --- a/app/src/styles/main.scss +++ b/app/src/styles/main.scss @@ -13,7 +13,6 @@ @use 'components/button'; @use 'components/form'; @use 'components/input'; -@use 'components/layouts'; @use 'components/lists'; @use 'components/moduleToggle'; @use 'components/templates'; @@ -22,11 +21,10 @@ @use 'components/visualization/ui/styledWidgets'; @use 'components/visualization/floating/common'; @use 'components/marketPlace/marketPlace'; -@use 'components/simulation/simulation'; @use 'components/menu/menu'; @use 'components/confirmationPopUp'; -@use 'components/analysis/analysis'; -@use 'components/analysis/ROISummary.scss'; +@use 'components/simulation/simulation'; +@use 'components/simulation/analysis'; @use 'components/logs/logs'; @use 'components/footer/footer.scss'; diff --git a/app/src/types/simulationTypes.d.ts b/app/src/types/simulationTypes.d.ts index cb71864..c7ccd8b 100644 --- a/app/src/types/simulationTypes.d.ts +++ b/app/src/types/simulationTypes.d.ts @@ -88,6 +88,7 @@ interface StoragePointSchema { actionType: "store"; materials: { materialName: string; materialId: string; }[]; storageCapacity: number; + triggers: TriggerSchema[]; }; } @@ -143,6 +144,10 @@ interface MachineStatus extends MachineEventSchema { isActive: boolean; idleTime: number; activeTime: number; + currentAction?: { + actionUuid: string; + actionName: string; + }; } interface ArmBotStatus extends RoboticArmEventSchema {