From 53ade999309259c94cc59b756e262b0bc991592a Mon Sep 17 00:00:00 2001 From: Nalvazhuthi Date: Fri, 22 Aug 2025 18:03:50 +0530 Subject: [PATCH 01/14] feat: Enhance Asset Management component with drop icon and improved styling --- .../hrm/assetManagement/AssetManagement.tsx | 7 +++++-- app/src/styles/layout/resourceManagement.scss | 12 +++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx b/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx index 2e2c665..d3df99d 100644 --- a/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx +++ b/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx @@ -61,7 +61,10 @@ const AssetManagement = () => {
{expandedAssetId === asset.id ? - + <> +
setExpandedAssetId(null)}>▾
+ + :
} @@ -98,7 +101,7 @@ const AssetManagement = () => {
-
setExpandedAssetId(null)}> +
View in Scene
diff --git a/app/src/styles/layout/resourceManagement.scss b/app/src/styles/layout/resourceManagement.scss index 400a98c..0b6d0c3 100644 --- a/app/src/styles/layout/resourceManagement.scss +++ b/app/src/styles/layout/resourceManagement.scss @@ -274,13 +274,14 @@ display: flex; flex-direction: column; gap: 6px; + position: relative; .assetManagement-card-wrapper { padding: 16px; border: 1px solid #564B69; border-radius: 20px; gap: 10px; - + position: relative; header { @@ -298,6 +299,14 @@ border-radius: 7px; @include flex-center; background: var(--background-color-button); + + } + + .drop-icon { + position: absolute; + top: 15px; + right: 17px; + cursor: pointer; } .asset-image { @@ -402,6 +411,7 @@ } .asset-estimate { + margin-top: 5px; display: flex; flex-direction: column; gap: 10px; From 7d8b106e08a8a64ac61de1c4f65e4374e200c0f1 Mon Sep 17 00:00:00 2001 From: Nalvazhuthi Date: Fri, 22 Aug 2025 18:07:11 +0530 Subject: [PATCH 02/14] fix: update package-lock.json to change devOptional to dev for several packages --- app/package-lock.json | 48 +++++++++++++------------------------------ 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index a1bf231..6f14ba4 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -2027,7 +2027,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -2039,7 +2039,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -4190,26 +4190,6 @@ "url": "https://github.com/sponsors/gregberge" } }, - "node_modules/@testing-library/dom": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", - "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@testing-library/jest-dom": { "version": "5.17.0", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", @@ -4321,25 +4301,25 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "devOptional": true + "dev": true }, "node_modules/@turf/along": { "version": "7.2.0", @@ -9110,7 +9090,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "devOptional": true + "dev": true }, "node_modules/cross-env": { "version": "7.0.3", @@ -9987,7 +9967,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.3.1" } @@ -15371,7 +15351,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "devOptional": true + "dev": true }, "node_modules/makeerror": { "version": "1.0.12", @@ -20906,7 +20886,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "devOptional": true, + "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -20949,7 +20929,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "devOptional": true, + "dev": true, "dependencies": { "acorn": "^8.11.0" }, @@ -20961,7 +20941,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": true + "dev": true }, "node_modules/tsconfig-paths": { "version": "3.15.0", @@ -21457,7 +21437,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "devOptional": true + "dev": true }, "node_modules/v8-to-istanbul": { "version": "8.1.1", @@ -22516,7 +22496,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } From decdaaf8c6d5d0d2c962d4a34ee96a65475c285d Mon Sep 17 00:00:00 2001 From: Vishnu Date: Sat, 23 Aug 2025 17:46:52 +0530 Subject: [PATCH 03/14] feat: updated thumbnail bug fixed --- app/src/modules/scene/scene.tsx | 28 ++++++++++++----------- app/src/utils/compressImage.ts | 39 +++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 app/src/utils/compressImage.ts diff --git a/app/src/modules/scene/scene.tsx b/app/src/modules/scene/scene.tsx index b5e6739..635edd2 100644 --- a/app/src/modules/scene/scene.tsx +++ b/app/src/modules/scene/scene.tsx @@ -14,6 +14,7 @@ import { getAllProjects } from "../../services/dashboard/getAllProjects"; import { getUserData } from "../../functions/getUserData"; import { useLoadingProgress, useSocketStore } from "../../store/builder/store"; import { Color, SRGBColorSpace } from "three"; +import { compressImage } from "../../utils/compressImage"; export default function Scene({ layout }: { readonly layout: "Main Layout" | "Comparison Layout"; }) { const map = useMemo(() => [ @@ -31,23 +32,24 @@ export default function Scene({ layout }: { readonly layout: "Main Layout" | "Co const { loadingProgress } = useLoadingProgress(); useEffect(() => { - if (!projectId && loadingProgress > 1) return; + if (!projectId || loadingProgress !== 0) return; getAllProjects(userId, organization).then((projects) => { if (!projects || !projects.Projects) return; let project = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id === projectId); const canvas = document.getElementById("sceneCanvas")?.getElementsByTagName("canvas")[0]; if (!canvas) return; - const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL("image/png"); - const updateProjects = { - projectId: project?._id, - organization, - userId, - projectName: project?.projectName, - thumbnail: screenshotDataUrl, - }; - if (projectSocket) { - projectSocket.emit("v1:project:update", updateProjects); - } + compressImage((canvas as HTMLCanvasElement)?.toDataURL("image/png")).then((screenshotDataUrl) => { + const updateProjects = { + projectId: project?._id, + organization, + userId, + projectName: project?.projectName, + thumbnail: screenshotDataUrl, + }; + if (projectSocket) { + projectSocket.emit("v1:project:update", updateProjects); + } + }); }).catch((err) => { console.error(err); }); @@ -64,7 +66,7 @@ export default function Scene({ layout }: { readonly layout: "Main Layout" | "Co onContextMenu={(e) => { e.preventDefault(); }} performance={{ min: 0.9, max: 1.0 }} onCreated={(e) => { e.scene.background = layout === "Main Layout" ? null : new Color(0x19191d); }} - gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true }} + gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true, preserveDrawingBuffer: true }} > diff --git a/app/src/utils/compressImage.ts b/app/src/utils/compressImage.ts new file mode 100644 index 0000000..3aafcea --- /dev/null +++ b/app/src/utils/compressImage.ts @@ -0,0 +1,39 @@ +export async function compressImage( + dataUrl: string, + maxWidth = 400, + maxHeight = 400 +): Promise { + return new Promise((resolve) => { + const img = new Image(); + img.src = dataUrl; + + img.onload = () => { + const { width, height } = img; + + // Calculate aspect ratio preserving resize + let newWidth = width; + let newHeight = height; + + if (width > height) { + if (width > maxWidth) { + newWidth = maxWidth; + newHeight = (height * maxWidth) / width; + } + } else { + if (height > maxHeight) { + newHeight = maxHeight; + newWidth = (width * maxHeight) / height; + } + } + + const offCanvas = document.createElement("canvas"); + const ctx = offCanvas.getContext("2d"); + + offCanvas.width = newWidth; + offCanvas.height = newHeight; + + ctx?.drawImage(img, 0, 0, newWidth, newHeight); + resolve(offCanvas.toDataURL("image/png")); + }; + }); +} From fad9c74730190f1629c4c79a0bb568e3ac183a68 Mon Sep 17 00:00:00 2001 From: Gomathi9520 Date: Mon, 25 Aug 2025 10:28:11 +0530 Subject: [PATCH 04/14] refactor: update navigation path and enhance asset/worker management components --- app/src/components/Dashboard/SidePannel.tsx | 2 +- .../resourceManagement/ResourceManagement.tsx | 8 - .../resourceManagement/hrm/Hrm.tsx | 233 +++++++++++------- .../hrm/assetManagement/AssetManagement.tsx | 145 ++++++++--- 4 files changed, 252 insertions(+), 136 deletions(-) diff --git a/app/src/components/Dashboard/SidePannel.tsx b/app/src/components/Dashboard/SidePannel.tsx index 89ce5ea..f38f0bc 100644 --- a/app/src/components/Dashboard/SidePannel.tsx +++ b/app/src/components/Dashboard/SidePannel.tsx @@ -64,7 +64,7 @@ const SidePannel: React.FC = ({ setActiveTab, activeTab }) => { const handleResponse = (data: any) => { if (data.message === "Project created successfully") { setLoadingProgress(1) - navigate(`/${data.data.projectId}`); + navigate(`/projects/${data.data.projectId}`); } projectSocket.off("v1-project:response:add", handleResponse); // Clean up }; diff --git a/app/src/components/layout/sidebarRight/resourceManagement/ResourceManagement.tsx b/app/src/components/layout/sidebarRight/resourceManagement/ResourceManagement.tsx index 3b0df8a..e5c0442 100644 --- a/app/src/components/layout/sidebarRight/resourceManagement/ResourceManagement.tsx +++ b/app/src/components/layout/sidebarRight/resourceManagement/ResourceManagement.tsx @@ -30,14 +30,6 @@ const ResourceManagement = () => {
{ }} /> -
- { }} - search={false} - /> -
{selectType === "assetManagement" ? : } diff --git a/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx b/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx index 2aeacc2..369c04f 100644 --- a/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx +++ b/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx @@ -1,96 +1,156 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' import { ClockThreeIcon, LocationPinIcon, TargetIcon } from '../../../../icons/ExportCommonIcons' +import { useSceneContext } from '../../../../../modules/scene/sceneContext'; +import { useProductContext } from '../../../../../modules/simulation/products/productContext'; +import RenameInput from '../../../../ui/inputs/RenameInput'; // import NavigateCatagory from '../NavigateCatagory' const Hrm = () => { const [selectedCard, setSelectedCard] = useState(0); + const [workers, setWorkers] = useState([]); - const employee_details = [ - { - "employee": { - image: "", - "name": "John Doe", - "employee_id": "HR-204", - "status": "Active", + const { productStore } = useSceneContext(); + const { products, getProductById } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); - }, - "task": { - "status": "Ongoing", - "title": "Inspecting Machine X", - "location": { - "floor": 4, - "zone": "B" - }, - "planned_time_hours": 6, - "time_spent_hours": 2, - "total_tasks": 12, - "completed_tasks": 3 - }, - "actions": [ - "Assign Task", - "Reassign Task", - "Pause", - "Emergency Stop" - ], - "location": "Floor 4 . Zone B" - }, - { - "employee": { - image: "", - "name": "Alice Smith", - "employee_id": "HR-205", - "status": "Active", + useEffect(() => { + if (selectedProduct) { + const productDetails = getProductById(selectedProduct.productUuid); + const workerDetails = productDetails?.eventDatas || []; - }, - "task": { - "status": "Ongoing", - "title": "Calibrating Sensor Y", - "location": { - "floor": 2, - "zone": "A" - }, - "planned_time_hours": 4, - "time_spent_hours": 1.5, - "total_tasks": 10, - "completed_tasks": 2 - }, - "actions": [ - "Assign Task", - "Reassign Task", - "Pause", - "Emergency Stop" - ], - "location": "Floor 4 . Zone B" - }, - { - "employee": { - image: "", - "name": "Michael Lee", - "employee_id": "HR-206", - "status": "Active", + const formattedWorkers = workerDetails + .filter((worker: any) => worker.type === "human") + .map((worker: any, index: number) => ({ + employee: { + image: "", + name: worker.modelName, + employee_id: `HR-${204 + index}`, + status: "Active", + }, + task: { + status: "Ongoing", + title: worker.taskTitle || "No Task Assigned", + location: { + floor: worker.floor || 0, + zone: worker.zone || "N/A" + }, + planned_time_hours: worker.plannedTime || 0, + time_spent_hours: worker.timeSpent || 0, + total_tasks: worker.totalTasks || 0, + completed_tasks: worker.completedTasks || 0 + }, + actions: [ + "Assign Task", + "Reassign Task", + "Pause", + "Emergency Stop" + ], + location: `Floor ${worker.floor || "-"} . Zone ${worker.zone || "-"}` + })); + + setWorkers(formattedWorkers); + } + }, [selectedProduct, getProductById]); + + useEffect(() => { + // console.log("Workers data updated:", workers); + }, [workers]); + + + + + // const employee_details = [ + // { + // "employee": { + // image: "", + // "name": "John Doe", + // "employee_id": "HR-204", + // "status": "Active", + + // }, + // "task": { + // "status": "Ongoing", + // "title": "Inspecting Machine X", + // "location": { + // "floor": 4, + // "zone": "B" + // }, + // "planned_time_hours": 6, + // "time_spent_hours": 2, + // "total_tasks": 12, + // "completed_tasks": 3 + // }, + // "actions": [ + // "Assign Task", + // "Reassign Task", + // "Pause", + // "Emergency Stop" + // ], + // "location": "Floor 4 . Zone B" + // }, + // { + // "employee": { + // image: "", + // "name": "Alice Smith", + // "employee_id": "HR-205", + // "status": "Active", + + // }, + // "task": { + // "status": "Ongoing", + // "title": "Calibrating Sensor Y", + // "location": { + // "floor": 2, + // "zone": "A" + // }, + // "planned_time_hours": 4, + // "time_spent_hours": 1.5, + // "total_tasks": 10, + // "completed_tasks": 2 + // }, + // "actions": [ + // "Assign Task", + // "Reassign Task", + // "Pause", + // "Emergency Stop" + // ], + // "location": "Floor 4 . Zone B" + // }, + // { + // "employee": { + // image: "", + // "name": "Michael Lee", + // "employee_id": "HR-206", + // "status": "Active", + + // }, + // "task": { + // "status": "Ongoing", + // "title": "Testing Conveyor Belt Z", + // "location": { + // "floor": 5, + // "zone": "C" + // }, + // "planned_time_hours": 5, + // "time_spent_hours": 3, + // "total_tasks": 8, + // "completed_tasks": 5 + // }, + // "actions": [ + // "Assign Task", + // "Reassign Task", + // "Pause", + // "Emergency Stop" + // ], + // "location": "Floor 4 . Zone B" + // }, + // ] + function handleRenameWorker(newName: string) { + // console.log('newName: ', newName); + + } - }, - "task": { - "status": "Ongoing", - "title": "Testing Conveyor Belt Z", - "location": { - "floor": 5, - "zone": "C" - }, - "planned_time_hours": 5, - "time_spent_hours": 3, - "total_tasks": 8, - "completed_tasks": 5 - }, - "actions": [ - "Assign Task", - "Reassign Task", - "Pause", - "Emergency Stop" - ], - "location": "Floor 4 . Zone B" - }, - ] return ( <> @@ -101,7 +161,7 @@ const Hrm = () => { /> */}
- {employee_details.map((employee, index) => ( + {workers.map((employee, index) => (
setSelectedCard(index)} @@ -114,7 +174,8 @@ const Hrm = () => {
-
{employee.employee.name}
+ {/*
{employee.employee.name}
*/} +
{employee.employee.employee_id}
diff --git a/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx b/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx index 4daa7aa..2cfe731 100644 --- a/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx +++ b/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx @@ -1,49 +1,105 @@ -import { useState } from 'react' +import { useEffect, useState } from 'react' // import NavigateCatagory from '../../NavigateCatagory' import { EyeIcon, ForkLiftIcon, KebabIcon, LocationPinIcon, RightHalfFillCircleIcon } from '../../../../../icons/ExportCommonIcons'; import assetImage from "../../../../../../assets/image/asset-image.png" +import { useSceneContext } from '../../../../../../modules/scene/sceneContext'; +import { useProductContext } from '../../../../../../modules/simulation/products/productContext'; +import RenameInput from '../../../../../ui/inputs/RenameInput'; const AssetManagement = () => { // const [selectedCategory, setSelectedCategory] = useState("All Assets"); const [expandedAssetId, setExpandedAssetId] = useState(null); + const [assets, setAssets] = useState([]); - const dummyAssets = [ - { - id: '1', - name: 'Forklift Model X200', - model: 'FLK-0025', - status: 'Online', - usageRate: 15, - level: 'Level 1', - image: assetImage, - description: 'Electric forklift used for moving goods and materials in warehouse operations.', - cost: 122000, - count: 5, - }, - { - id: '2', - name: 'Warehouse Robot WR-300', - model: 'WRB-3001', - status: 'Online', - usageRate: 50, - level: 'Level 2', - image: assetImage, - description: 'Automated robot for handling packages and inventory in the warehouse.', - cost: 85000, - count: 3, - }, - { - id: '3', - name: 'Conveyor Belt System CB-150', - model: 'CBS-150X', - status: 'Online', - usageRate: 95, - level: 'Level 3', - image: assetImage, - description: 'High-speed conveyor belt system for efficient material handling.', - cost: 45000, - count: 2, - }, - ]; + const { productStore } = useSceneContext(); + const { products, getProductById } = productStore(); + const { selectedProductStore } = useProductContext(); + const { selectedProduct } = selectedProductStore(); + + + + useEffect(() => { + if (selectedProduct) { + const productDetails = getProductById(selectedProduct.productUuid); + const productAssets = productDetails?.eventDatas || []; + const grouped: Record = {}; + productAssets.forEach((asset: any) => { + if (asset.type === "storageUnit" || asset.type === "human") return; + if (!grouped[asset.modelName]) { + grouped[asset.modelName] = { + id: asset.assetId, + name: asset.modelName, + model: asset.modelCode || "N/A", + status: asset.status || "Online", + usageRate: asset.usageRate || 15, + level: asset.level || "Level 1", + image: assetImage, + description: asset.description || "No description", + cost: asset.cost || 0, + count: 1, + }; + } else { + grouped[asset.modelName].count += 1; + } + }); + + setAssets(Object.values(grouped)); + } + }, [selectedProduct]); + + function handleRenameAsset(newName: string) { + // console.log('newName: ', newName); + // if (expandedAssetId) { + // setAssets(prevAssets => + // prevAssets.map(asset => + // asset.id === expandedAssetId ? { ...asset, name: newName } : asset + // ) + // ); + // } + } + + useEffect(() => { + + + }, [assets]); + + // const dummyAssets = [ + // { + // id: '1', + // name: 'Forklift Model X200', + // model: 'FLK-0025', + // status: 'Online', + // usageRate: 15, + // level: 'Level 1', + // image: assetImage, + // description: 'Electric forklift used for moving goods and materials in warehouse operations.', + // cost: 122000, + // count: 5, + // }, + // { + // id: '2', + // name: 'Warehouse Robot WR-300', + // model: 'WRB-3001', + // status: 'Online', + // usageRate: 50, + // level: 'Level 2', + // image: assetImage, + // description: 'Automated robot for handling packages and inventory in the warehouse.', + // cost: 85000, + // count: 3, + // }, + // { + // id: '3', + // name: 'Conveyor Belt System CB-150', + // model: 'CBS-150X', + // status: 'Online', + // usageRate: 95, + // level: 'Level 3', + // image: assetImage, + // description: 'High-speed conveyor belt system for efficient material handling.', + // cost: 45000, + // count: 2, + // }, + // ]; return ( @@ -55,7 +111,7 @@ const AssetManagement = () => { /> */}
- {dummyAssets.map((asset, index) => ( + {assets.map((asset, index) => (
@@ -67,7 +123,14 @@ const AssetManagement = () => { }
-
{asset.name}
+ {/*
{asset.name}
*/} + + {asset.count !== 1 &&
+ x + {asset.count} +
} + +
{asset.model}
From 0601c9743faf5611f70a0eb88a7d1167e11bae09 Mon Sep 17 00:00:00 2001 From: Vishnu Date: Mon, 25 Aug 2025 10:40:59 +0530 Subject: [PATCH 05/14] refactor: style update resource manager --- app/src/styles/layout/resourceManagement.scss | 901 +++++++++--------- 1 file changed, 445 insertions(+), 456 deletions(-) diff --git a/app/src/styles/layout/resourceManagement.scss b/app/src/styles/layout/resourceManagement.scss index 400a98c..eef996c 100644 --- a/app/src/styles/layout/resourceManagement.scss +++ b/app/src/styles/layout/resourceManagement.scss @@ -1,491 +1,480 @@ @use "../abstracts/variables" as *; @use "../abstracts/mixins" as *; - - .resourceManagement-container { - .navigation-wrapper { - @include flex-space-between; - justify-content: space-around; + .navigation-wrapper { + @include flex-space-between; + justify-content: space-around; - .navigation { + .navigation { + padding: 4px 12px; + border-radius: 20px; + text-wrap: nowrap; + margin: 6px 0; + cursor: pointer; - padding: 4px 12px; - border-radius: 20px; - text-wrap: nowrap; - margin: 6px 0; - cursor: pointer; + &.active { + background: var(--background-color-button); + } + } + } - &.active { - background: var(--background-color-button); - } - } + .search-container { + position: relative; + padding: 4px 2px; + .search-wrapper { + padding: 0; + + input { + padding-right: 85px; + } } - .search-container { + .select-catagory { + position: absolute; + top: 50%; + right: 8px; + transform: translate(0, -50%); + z-index: 10; + + .regularDropdown-container { + padding: 2px 8px; + } + } + } + + .category-wrapper { + display: flex; + gap: 12px; + width: 100%; + overflow: auto; + padding: 8px 10px; + + .category { + text-wrap: nowrap; + position: relative; + cursor: pointer; + color: var(--text-disabled); + + &.active { + color: var(--text-color); + + &::after { + content: ""; + position: absolute; + bottom: -6px; + left: 0; + width: 100%; + height: 2px; + border-radius: 100px; + background: var(--background-color-button); + } + } + } + } + + .assetManagement-wrapper { + max-height: calc(62vh - 12px); + overflow: auto; + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 7px; + // padding: 7px 2px; + } + + // HRM + .hrm-container { + .analysis-wrapper { + border-radius: 20px; + padding: 16px; + + display: flex; + flex-direction: column; + gap: 14px; + + outline: 1px solid var(--border-color); + outline-offset: -1px; + background: var(--background-color); + + &.active { + outline: 1px solid var(--Color-Hover, #ccacff); + } + + header { position: relative; - padding: 4px 2px; + @include flex-space-between; + padding: 3px 0; - .search-wrapper { - padding: 0; + .user-details { + display: flex; + gap: 6px; - input { - padding-right: 85px; - } - } - - .select-catagory { - position: absolute; - top: 50%; - right: 8px; - transform: translate(0, -50%); - z-index: 10; - - .regularDropdown-container { - padding: 2px 8px; - } - } - } - - .category-wrapper { - display: flex; - gap: 12px; - width: 100%; - overflow: auto; - padding: 8px 10px; - - .category { - text-wrap: nowrap; + .user-image-wrapper { + width: 28px; + height: 28px; + border-radius: 50%; + background-color: #fff; position: relative; - cursor: pointer; - color: var(--text-disabled); - &.active { - color: var(--text-color); + .status { + border-radius: 50%; + width: 6px; + height: 6px; + outline: 1px solid #2f2c32; - &::after { - content: ""; - position: absolute; - bottom: -6px; - left: 0; - width: 100%; - height: 2px; - border-radius: 100px; - background: var(--background-color-button); - } + position: absolute; + bottom: 0; + right: 0; + &.Active { + background-color: #44e5c6; + } } - } - } + } - .assetManagement-wrapper { - max-height: calc(62vh - 12px); - overflow: auto; + .details { + .employee-id { + color: #b7b7c6; + font-size: $tiny; + } + } + } + + .see-more { + padding: 4px 12px; + border-radius: 20px; + text-wrap: nowrap; + margin: 6px 0; + cursor: pointer; + background: var(--background-color-button); + } + + &::after { + content: ""; + position: absolute; + bottom: -7px; + left: 0; + width: 100%; + height: 1px; + background-color: #6f6f7a; + } + } + + .content { display: flex; flex-direction: column; - gap: 12px; - margin-top: 7px; - // padding: 7px 2px; - } + gap: 4px; - // HRM - .hrm-container { - .analysis-wrapper { - border-radius: 20px; - padding: 16px; + .task-info { + padding: 8px 0; + display: flex; + flex-direction: column; + gap: 6px; + .task-wrapper { display: flex; - flex-direction: column; + justify-content: space-between; + align-items: center; + + .task-label { + display: flex; + align-items: center; + gap: 3px; + + .label-text { + color: #b7b7c6; + } + } + } + } + + .task-stats { + display: grid; + grid-template-columns: repeat(1, 1fr); // Two equal-width columns + gap: 4px; + + .stat-item { + border-radius: 100px; + @include flex-space-between; + background: linear-gradient( + 162.53deg, + rgba(51, 51, 51, 0.7) 0%, + rgba(45, 36, 55, 0.7) 106.84% + ); + border: 1px solid #ffffff0d; + padding: 6px; + + .stat-wrapper { + display: flex; + align-items: center; + gap: 4px; + } + + span, + .stat-value { + font-size: 10px; + display: flex; + } + } + } + + .location-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + padding: 6px 0; + + .location-header { + display: flex; + gap: 6px; + + .icon { + display: flex; + } + + .header { + font-size: 12px; + color: #b7b7c6; + } + } + } + + .task-actions { + display: grid; + grid-template-columns: repeat(2, 1fr); // Two equal-width columns + gap: 4px; + margin-top: 3px; + + button { + line-height: 133%; + font-size: 11px; + border: 1px solid var(--Linear-Border, #564b69); + border-radius: 100px; + padding: 4px 0; + + &:last-child { + background-color: #cc2c1e; + } + } + } + } + } + } + + // ASSET MANAGEMENT + .assetManagement-container { + display: flex; + flex-direction: column; + gap: 6px; + + .assetManagement-card-wrapper { + padding: 16px; + border: 1px solid #564b69; + border-radius: 20px; + gap: 10px; + + header { + border-bottom: 1px solid #595965; + padding-bottom: 8px; + + .header-wrapper { + display: flex; + gap: 8px; + + .icon { + min-width: 28px; + height: 28px; + border-radius: 7px; + @include flex-center; + background: var(--background-color-button); + } + + .asset-image { + width: 114px; + height: 112px; + border-radius: 15.2px; + object-fit: cover; + } + + .asset-details-container { + width: 100%; + @include flex-space-between; + + .asset-details { + display: flex; + gap: 4px; + width: 100%; + max-width: 160px; + .input-value { + width: 100%; + } + .asset-model { + color: var(--text-disabled); + display: none; + } + } + } + + .asset-status-wrapper { + padding: 4px 8px; + border: 1px solid var(--text-color-dark, #f3f3fdd9); + border-radius: 100px; + @include flex-space-between; + gap: 4px; + + .indication { + width: 6px; + height: 6px; + border-radius: 100%; + + &.Online { + background-color: #44e5c6; + } + } + + .status { + font-size: $small; + } + } + } + } + + .asset-contents { + display: flex; + flex-direction: column; + gap: 3px; + + .asset-wrapper { + @include flex-space-between; + padding: 6px 0; + gap: 20px; + + .key-wrapper, + .viewMore { + display: flex; + align-items: center; + gap: 6px; + + .icon { + @include flex-center; + } + } + + .viewMore { + padding: 8px; + border-radius: 100px; + background: var(--background-color-button); + cursor: pointer; + } + + .progress-wrapper { + flex: 1; + @include flex-space-between; + gap: 4px; + + .progress-bar { + width: 100%; + height: 5px; + border-radius: 20px; + background-color: #6f6f7a; + position: relative; + + .filled-value { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 10px; + background-color: #ccacff; + border-radius: 20px; + } + } + } + } + } + + .asset-estimate { + display: flex; + flex-direction: column; + gap: 10px; + + &__label { + color: #b7b7c6; + font-size: 14px; + } + + &__value { + font-weight: 500; + font-size: 16px; + } + + &__unit-cost { + display: flex; + flex-direction: column; + gap: 4px; + } + + &__breakdown { + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 2px; + } + + &__view-button { + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + gap: 4px; + background-color: var(--background-color-button); + border-radius: 20px; + padding: 8px 0; + } + + &__view-text { + font-weight: 500; + // color: #4A4AFF; + } + } + + &.openViewMore { + outline-offset: -1px; + outline: 1px solid var(--Color-Hover, #ccacff); + + header { + display: flex; + flex-direction: column; + gap: 6px; + + .header-wrapper { gap: 14px; - outline: 1px solid var(--border-color); - outline-offset: -1px; - background: var(--background-color); + .asset-details-container { + flex-direction: column; + align-items: start; + justify-content: start; - &.active { - outline: 1px solid var(--Color-Hover, #CCACFF); - } - - header { - position: relative; - @include flex-space-between; - padding: 3px 0; - - .user-details { - display: flex; - gap: 6px; - - .user-image-wrapper { - width: 28px; - height: 28px; - border-radius: 50%; - background-color: #fff; - position: relative; - - .status { - border-radius: 50%; - width: 6px; - height: 6px; - outline: 1px solid #2F2C32; - - position: absolute; - bottom: 0; - right: 0; - - &.Active { - background-color: #44E5C6; - } - } - } - - .details { - .employee-id { - color: #B7B7C6; - font-size: $tiny; - - } - } - } - - .see-more { - - padding: 4px 12px; - border-radius: 20px; - text-wrap: nowrap; - margin: 6px 0; - cursor: pointer; - background: var(--background-color-button); - } - - &::after { - content: ""; - position: absolute; - bottom: -7px; - left: 0; - width: 100%; - height: 1px; - background-color: #6F6F7A; - } - } - - .content { + .asset-details { display: flex; flex-direction: column; gap: 4px; - - .task-info { - padding: 8px 0; - display: flex; - flex-direction: column; - gap: 6px; - - .task-wrapper { - display: flex; - justify-content: space-between; - align-items: center; - - .task-label { - display: flex; - align-items: center; - gap: 3px; - - .label-text { - color: #B7B7C6; - } - } - } - } - - .task-stats { - display: grid; - grid-template-columns: repeat(1, 1fr); // Two equal-width columns - gap: 4px; - - .stat-item { - border-radius: 100px; - @include flex-space-between; - background: linear-gradient(162.53deg, - rgba(51, 51, 51, 0.7) 0%, - rgba(45, 36, 55, 0.7) 106.84%); - border: 1px solid #FFFFFF0D; - padding: 6px; - - .stat-wrapper { - display: flex; - align-items: center; - gap: 4px; - } - - span, - .stat-value { - font-size: 10px; - display: flex; - } - } - } - - .location-wrapper { - display: flex; - align-items: center; - justify-content: space-between; - padding: 6px 0; - - .location-header { - display: flex; - gap: 6px; - - .icon { - display: flex; - } - - .header { - font-size: 12px; - color: #B7B7C6; - } - } - } - - .task-actions { - display: grid; - grid-template-columns: repeat(2, 1fr); // Two equal-width columns - gap: 4px; - margin-top: 3px; - - button { - line-height: 133%; - font-size: 11px; - border: 1px solid var(--Linear-Border, #564B69); - border-radius: 100px; - padding: 4px 0; - - &:last-child { - background-color: #CC2C1E; - } - } + max-width: 144px; + .input-value { + text-wrap: wrap; + font-size: 1rem; } + } + .asset-status-wrapper { + margin-top: 8px; + } } + } } + } } - - // ASSET MANAGEMENT - .assetManagement-container { - display: flex; - flex-direction: column; - gap: 6px; - - .assetManagement-card-wrapper { - padding: 16px; - border: 1px solid #564B69; - border-radius: 20px; - gap: 10px; - - - - header { - border-bottom: 1px solid #595965; - padding-bottom: 8px; - - - .header-wrapper { - display: flex; - gap: 8px; - - .icon { - min-width: 28px; - height: 28px; - border-radius: 7px; - @include flex-center; - background: var(--background-color-button); - } - - .asset-image { - width: 114px; - height: 112px; - border-radius: 15.2px; - object-fit: cover; - } - - .asset-details-container { - width: 100%; - @include flex-space-between; - - .asset-details { - - // .asset-name{ - // overflow: hidden; - // } - .asset-model { - color: var(--text-disabled); - - } - } - } - - .asset-status-wrapper { - padding: 4px 8px; - border: 1px solid var(--text-color-dark, #F3F3FDD9); - border-radius: 100px; - @include flex-space-between; - gap: 4px; - - .indication { - width: 6px; - height: 6px; - border-radius: 100%; - - &.Online { - background-color: #44E5C6; - } - } - - .status { - font-size: $small; - } - } - } - } - - .asset-contents { - display: flex; - flex-direction: column; - gap: 3px; - - .asset-wrapper { - @include flex-space-between; - padding: 6px 0; - gap: 20px; - - .key-wrapper, - .viewMore { - display: flex; - align-items: center; - gap: 6px; - - .icon { - @include flex-center; - } - } - - .viewMore { - padding: 8px; - border-radius: 100px; - background: var(--background-color-button); - cursor: pointer; - } - - .progress-wrapper { - flex: 1; - @include flex-space-between; - gap: 4px; - - .progress-bar { - width: 100%; - height: 5px; - border-radius: 20px; - background-color: #6F6F7A; - position: relative; - - .filled-value { - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 10px; - background-color: #CCACFF; - border-radius: 20px; - } - } - } - } - } - - .asset-estimate { - display: flex; - flex-direction: column; - gap: 10px; - - &__label { - color: #B7B7C6; - font-size: 14px; - } - - &__value { - font-weight: 500; - font-size: 16px; - } - - &__unit-cost { - display: flex; - flex-direction: column; - gap: 4px; - } - - &__breakdown { - display: flex; - flex-direction: row; - justify-content: space-between; - gap: 2px; - } - - &__view-button { - cursor: pointer; - display: flex; - justify-content: center; - align-items: center; - gap: 4px; - background-color: var(--background-color-button); - border-radius: 20px; - padding: 8px 0; - } - - &__view-text { - font-weight: 500; - // color: #4A4AFF; - } - } - - &.openViewMore { - outline-offset: -1px; - outline: 1px solid var(--Color-Hover, #CCACFF); - - header { - display: flex; - flex-direction: column; - gap: 6px; - - .header-wrapper { - - gap: 20px; - - .asset-details-container { - flex-direction: column; - align-items: start; - justify-content: center; - - .asset-details { - display: flex; - flex-direction: column; - gap: 4px; - - .asset-name { - font-size: 16px; - } - } - - .asset-status-wrapper { - margin-top: 4px; - } - } - } - } - - - - } - - } - } - -} \ No newline at end of file + } +} From 50e4c1a2a696d69aba89c7860bf2aebf0dbc2a70 Mon Sep 17 00:00:00 2001 From: Vishnu Date: Mon, 25 Aug 2025 11:34:25 +0530 Subject: [PATCH 06/14] update: transform tool ui added --- app/src/components/ui/Tools.tsx | 46 ++++++++++++++----- .../modules/scene/tools/measurementTool.tsx | 1 + app/src/styles/components/tools.scss | 1 + 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/app/src/components/ui/Tools.tsx b/app/src/components/ui/Tools.tsx index e834f28..d5d49e4 100644 --- a/app/src/components/ui/Tools.tsx +++ b/app/src/components/ui/Tools.tsx @@ -36,6 +36,7 @@ import { } from "../../store/visualization/useDroppedObjectsStore"; import { useParams } from "react-router-dom"; import { useVersionContext } from "../../modules/builder/version/versionContext"; +import { MoveIcon, RotateIcon } from "../icons/ShortcutIcons"; // Utility component const ToolButton = ({ @@ -65,12 +66,8 @@ const Tools: React.FC = () => { const { isPlaying, setIsPlaying } = usePlayButtonStore(); const { showShortcuts } = useShortcutStore(); - const { - activeTool, - setActiveTool, - setToolMode, - setAddAction, - } = useStoreHooks(); + const { activeTool, setActiveTool, setToolMode, setAddAction } = + useStoreHooks(); const { setActiveSubTool, activeSubTool } = useActiveSubTool(); const { setSelectedWallItem } = useSelectedWallItem(); @@ -81,14 +78,15 @@ const Tools: React.FC = () => { const { selectedZone } = useSelectedZoneStore(); const { floatingWidget } = useFloatingWidget(); const { widgets3D } = use3DWidget(); - const { visualizationSocket } = useSocketStore(); - const dropdownRef = useRef(null); - const [openDrop, setOpenDrop] = useState(false); + const { visualizationSocket } = useSocketStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); + const dropdownRef = useRef(null); + const [openDrop, setOpenDrop] = useState(false); + // 1. Set UI toggles on initial render useEffect(() => { setToggleUI( @@ -155,7 +153,7 @@ const Tools: React.FC = () => { if (!is2D) setAddAction("Pillar"); break; case "delete": - is2D ? setToolMode('2D-Delete') : setToolMode('3D-Delete'); + is2D ? setToolMode("2D-Delete") : setToolMode("3D-Delete"); break; } }; @@ -251,7 +249,7 @@ const Tools: React.FC = () => { templates, visualizationSocket, projectId, - versionId: selectedVersion?.versionId || '' + versionId: selectedVersion?.versionId || "", }) } /> @@ -278,6 +276,10 @@ const Tools: React.FC = () => { return FreeMoveIcon; case "delete": return DeleteIcon; + case "move": + return MoveIcon; + case "rotate": + return RotateIcon; default: return CursorIcon; } @@ -304,6 +306,10 @@ const Tools: React.FC = () => { return ; case "delete": return ; + case "move": + return ; + case "rotate": + return ; default: return null; } @@ -362,6 +368,24 @@ const Tools: React.FC = () => { )}
+ {activeModule !== "visualization" && ( + <> +
+
+ {["move", "rotate"].map((tool) => ( + setActiveTool(tool)} + /> + ))} +
+ + )} +
{activeModule === "builder" && renderBuilderTools()} {activeModule === "simulation" && renderSimulationTools()} diff --git a/app/src/modules/scene/tools/measurementTool.tsx b/app/src/modules/scene/tools/measurementTool.tsx index 1da41b3..393f533 100644 --- a/app/src/modules/scene/tools/measurementTool.tsx +++ b/app/src/modules/scene/tools/measurementTool.tsx @@ -100,6 +100,7 @@ const MeasurementTool = () => { canvasElement.removeEventListener("pointerup", onMouseUp); canvasElement.removeEventListener("contextmenu", onContextMenu); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [toolMode, camera, raycaster, pointer, scene, points]); useFrame(() => { diff --git a/app/src/styles/components/tools.scss b/app/src/styles/components/tools.scss index c07f11a..e01160d 100644 --- a/app/src/styles/components/tools.scss +++ b/app/src/styles/components/tools.scss @@ -31,6 +31,7 @@ } .draw-tools, + .transform-tools, .general-options, .activeDropicon { @include flex-center; From 706f587e72fdcdbfe429cbe49b3d395ca16eb84a Mon Sep 17 00:00:00 2001 From: Vishnu Date: Mon, 25 Aug 2025 12:31:05 +0530 Subject: [PATCH 07/14] refactor: enhance measurement tool functionality and styling --- .../modules/scene/tools/measurementTool.tsx | 395 ++++++++---------- app/src/styles/scene/scene.scss | 14 +- 2 files changed, 183 insertions(+), 226 deletions(-) diff --git a/app/src/modules/scene/tools/measurementTool.tsx b/app/src/modules/scene/tools/measurementTool.tsx index 393f533..4ca56fd 100644 --- a/app/src/modules/scene/tools/measurementTool.tsx +++ b/app/src/modules/scene/tools/measurementTool.tsx @@ -2,241 +2,188 @@ import * as THREE from "three"; import { useEffect, useRef, useState } from "react"; import { useThree, useFrame } from "@react-three/fiber"; import { useToolMode } from "../../../store/builder/store"; -import { Html } from "@react-three/drei"; +import { Html, Line } from "@react-three/drei"; const MeasurementTool = () => { - const { gl, raycaster, pointer, camera, scene } = useThree(); - const { toolMode } = useToolMode(); + const { gl, raycaster, pointer, camera, scene } = useThree(); + const { toolMode } = useToolMode(); - const [points, setPoints] = useState([]); - const [tubeGeometry, setTubeGeometry] = useState( - null - ); - const groupRef = useRef(null); - const [startConePosition, setStartConePosition] = - useState(null); - const [endConePosition, setEndConePosition] = useState( - null - ); - const [startConeQuaternion, setStartConeQuaternion] = useState( - new THREE.Quaternion() - ); - const [endConeQuaternion, setEndConeQuaternion] = useState( - new THREE.Quaternion() - ); - const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 }); + const [points, setPoints] = useState([]); + const [linePoints, setLinePoints] = useState(null); + const groupRef = useRef(null); - const MIN_RADIUS = 0.001, MAX_RADIUS = 0.1; - const MIN_CONE_RADIUS = 0.01, MAX_CONE_RADIUS = 0.4; - const MIN_CONE_HEIGHT = 0.035, MAX_CONE_HEIGHT = 2.0; + useEffect(() => { + const canvasElement = gl.domElement; + let drag = false; + let isLeftMouseDown = false; - useEffect(() => { - const canvasElement = gl.domElement; - let drag = false; - let isLeftMouseDown = false; - - const onMouseDown = () => { - isLeftMouseDown = true; - drag = false; - }; - - const onMouseUp = (evt: any) => { - isLeftMouseDown = false; - if (evt.button === 0 && !drag) { - raycaster.setFromCamera(pointer, camera); - 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.name.includes("zonePlane") && - !intersect.object.name.includes("SelectionGroup") && - !intersect.object.name.includes("selectionAssetGroup") && - !intersect.object.name.includes("SelectionGroupBoundingBoxLine") && - !intersect.object.name.includes("SelectionGroupBoundingBox") && - !intersect.object.name.includes("SelectionGroupBoundingLine") && - intersect.object.type !== "GridHelper" - ); - - if (intersects.length > 0) { - const intersectionPoint = intersects[0].point.clone(); - if (points.length < 2) { - setPoints([...points, intersectionPoint]); - } else { - setPoints([intersectionPoint]); - } - } - } - }; - - const onMouseMove = () => { - if (isLeftMouseDown) drag = true; - }; - - const onContextMenu = (evt: any) => { - evt.preventDefault(); - if (!drag) { - evt.preventDefault(); - setPoints([]); - setTubeGeometry(null); - } - }; - - if (toolMode === "MeasurementScale") { - canvasElement.addEventListener("pointerdown", onMouseDown); - canvasElement.addEventListener("pointermove", onMouseMove); - canvasElement.addEventListener("pointerup", onMouseUp); - canvasElement.addEventListener("contextmenu", onContextMenu); - } else { - resetMeasurement(); - setPoints([]); - } - - return () => { - canvasElement.removeEventListener("pointerdown", onMouseDown); - canvasElement.removeEventListener("pointermove", onMouseMove); - canvasElement.removeEventListener("pointerup", onMouseUp); - canvasElement.removeEventListener("contextmenu", onContextMenu); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [toolMode, camera, raycaster, pointer, scene, points]); - - useFrame(() => { - if (points.length === 1) { - raycaster.setFromCamera(pointer, camera); - 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.name.includes("zonePlane") && - !intersect.object.name.includes("SelectionGroup") && - !intersect.object.name.includes("selectionAssetGroup") && - !intersect.object.name.includes("SelectionGroupBoundingBoxLine") && - !intersect.object.name.includes("SelectionGroupBoundingBox") && - !intersect.object.name.includes("SelectionGroupBoundingLine") && - intersect.object.type !== "GridHelper" - ); - - if (intersects.length > 0) { - updateMeasurement(points[0], intersects[0].point); - } - } else if (points.length === 2) { - updateMeasurement(points[0], points[1]); - } else { - resetMeasurement(); - } - }); - - const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => { - const distance = start.distanceTo(end); - - const radius = THREE.MathUtils.clamp(distance * 0.02, MIN_RADIUS, MAX_RADIUS); - const coneRadius = THREE.MathUtils.clamp(distance * 0.05, MIN_CONE_RADIUS, MAX_CONE_RADIUS); - const coneHeight = THREE.MathUtils.clamp(distance * 0.2, MIN_CONE_HEIGHT, MAX_CONE_HEIGHT); - - setConeSize({ radius: coneRadius, height: coneHeight }); - - const direction = new THREE.Vector3().subVectors(end, start).normalize(); - - const offset = direction.clone().multiplyScalar(coneHeight * 0.5); - - let tubeStart = start.clone().add(offset); - let tubeEnd = end.clone().sub(offset); - - tubeStart.y = Math.max(tubeStart.y, 0); - tubeEnd.y = Math.max(tubeEnd.y, 0); - - const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]); - setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false)); - - setStartConePosition(tubeStart); - setEndConePosition(tubeEnd); - setStartConeQuaternion(getArrowOrientation(start, end)); - setEndConeQuaternion(getArrowOrientation(end, start)); + const onMouseDown = () => { + isLeftMouseDown = true; + drag = false; }; - const resetMeasurement = () => { - setTubeGeometry(null); - setStartConePosition(null); - setEndConePosition(null); - }; + const onMouseUp = (evt: any) => { + isLeftMouseDown = false; + if (evt.button === 0 && !drag) { + raycaster.setFromCamera(pointer, camera); + 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.name.includes("zonePlane") && + !intersect.object.name.includes("SelectionGroup") && + !intersect.object.name.includes("selectionAssetGroup") && + !intersect.object.name.includes( + "SelectionGroupBoundingBoxLine" + ) && + !intersect.object.name.includes("SelectionGroupBoundingBox") && + !intersect.object.name.includes("SelectionGroupBoundingLine") && + intersect.object.type !== "GridHelper" + ); - const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => { - const direction = new THREE.Vector3() - .subVectors(end, start) - .normalize() - .negate(); - const quaternion = new THREE.Quaternion(); - quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction); - return quaternion; - }; - - useEffect(() => { - if (points.length === 2) { - // console.log(points[0].distanceTo(points[1])); + if (intersects.length > 0) { + const intersectionPoint = intersects[0].point.clone(); + if (points.length < 2) { + setPoints([...points, intersectionPoint]); + } else { + setPoints([intersectionPoint]); + } } - }, [points]); + } + }; - return ( - - {startConePosition && ( - - - - - )} - {endConePosition && ( - - - - - )} - {tubeGeometry && ( - - - - )} + const onMouseMove = () => { + if (isLeftMouseDown) drag = true; + }; - {startConePosition && endConePosition && ( - -
- {(startConePosition.distanceTo(endConePosition) + (coneSize.height)).toFixed(2)} m -
- - )} -
- ); + const onContextMenu = (evt: any) => { + evt.preventDefault(); + if (!drag) { + setPoints([]); + setLinePoints(null); + } + }; + + if (toolMode === "MeasurementScale") { + canvasElement.addEventListener("pointerdown", onMouseDown); + canvasElement.addEventListener("pointermove", onMouseMove); + canvasElement.addEventListener("pointerup", onMouseUp); + canvasElement.addEventListener("contextmenu", onContextMenu); + } else { + setPoints([]); + setLinePoints(null); + } + + return () => { + canvasElement.removeEventListener("pointerdown", onMouseDown); + canvasElement.removeEventListener("pointermove", onMouseMove); + canvasElement.removeEventListener("pointerup", onMouseUp); + canvasElement.removeEventListener("contextmenu", onContextMenu); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [toolMode, camera, raycaster, pointer, scene, points]); + + useFrame(() => { + if (points.length === 1) { + // live preview for second point + raycaster.setFromCamera(pointer, camera); + 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.name.includes("zonePlane") && + !intersect.object.name.includes("SelectionGroup") && + !intersect.object.name.includes("selectionAssetGroup") && + !intersect.object.name.includes("SelectionGroupBoundingBoxLine") && + !intersect.object.name.includes("SelectionGroupBoundingBox") && + !intersect.object.name.includes("SelectionGroupBoundingLine") && + intersect.object.type !== "GridHelper" + ); + + if (intersects.length > 0) { + const tempEnd = intersects[0].point.clone(); + updateMeasurement(points[0], tempEnd); + } + } else if (points.length === 2) { + // second point already fixed + updateMeasurement(points[0], points[1]); + } else { + setLinePoints(null); + } + }); + + const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => { + setLinePoints([start.clone(), end.clone()]); + }; + + return ( + + {linePoints && ( + <> + {/* Outline line */} + + + {/* Main line */} + + + )} + + {points.map((point, index) => ( + +
+ + ))} + + {linePoints && linePoints.length === 2 && ( + +
{linePoints[0].distanceTo(linePoints[1]).toFixed(2)} m
+ + )} +
+ ); }; export default MeasurementTool; diff --git a/app/src/styles/scene/scene.scss b/app/src/styles/scene/scene.scss index 6358c81..dee2536 100644 --- a/app/src/styles/scene/scene.scss +++ b/app/src/styles/scene/scene.scss @@ -130,15 +130,25 @@ svg { display: none; } - .c-jiwtRJ{ + .c-jiwtRJ { align-items: center; } } -.stats{ +.stats { top: auto !important; bottom: 36px !important; left: 12px !important; border-radius: 6px; overflow: hidden; } + +.measurement-point { + height: 12px; + width: 12px; + border-radius: 50%; + background: #b18ef1; + outline: 2px solid black; + outline-offset: -1px; + transform: translate(-50%, -50%); +} From affffe09c801ab0c17fef12fea0a11621cc1803c Mon Sep 17 00:00:00 2001 From: Vishnu Date: Mon, 25 Aug 2025 14:08:25 +0530 Subject: [PATCH 08/14] feat: measurement tool axis lock added, style update, no animation ui update --- .../properties/AssetProperties.tsx | 11 +- .../contextControls/contextControls.tsx | 441 +++++++++--------- .../modules/scene/tools/measurementTool.tsx | 93 +++- app/src/styles/layout/sidebar.scss | 4 + 4 files changed, 332 insertions(+), 217 deletions(-) diff --git a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx index 4f76584..5d0a97c 100644 --- a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx @@ -103,9 +103,16 @@ const AssetProperties: React.FC = () => {
Animations
- {assets.map((asset) => { + {assets.map((asset, i) => { if (asset.modelUuid !== selectedFloorItem.uuid || !asset.animations) - return null; + return ( + i === 0 && ( +
+ Looks like there are no preset animations yet. Stay tuned for + future additions! +
+ ) + ); return asset.animations.map((animation, index) => (
diff --git a/app/src/modules/scene/controls/contextControls/contextControls.tsx b/app/src/modules/scene/controls/contextControls/contextControls.tsx index 1d0bbfa..142f8d1 100644 --- a/app/src/modules/scene/controls/contextControls/contextControls.tsx +++ b/app/src/modules/scene/controls/contextControls/contextControls.tsx @@ -1,222 +1,243 @@ -import { useEffect, useRef, useState } from 'react'; -import { useThree } from '@react-three/fiber'; -import { CameraControls, Html, ScreenSpace } from '@react-three/drei'; -import { useContextActionStore, useRenameModeStore, useSelectedAssets } from '../../../../store/builder/store'; -import ContextMenu from '../../../../components/ui/menu/contextMenu'; +import { useEffect, useRef, useState } from "react"; +import { useThree } from "@react-three/fiber"; +import { CameraControls, Html, ScreenSpace } from "@react-three/drei"; +import { + useContextActionStore, + useRenameModeStore, + useSelectedAssets, +} from "../../../../store/builder/store"; +import ContextMenu from "../../../../components/ui/menu/contextMenu"; function ContextControls() { - const { gl, controls } = useThree(); - const [canRender, setCanRender] = useState(false); - const [visibility, setVisibility] = useState({ rename: true, focus: true, flipX: true, flipZ: true, move: true, rotate: true, duplicate: true, copy: true, paste: true, modifier: false, group: false, array: false, delete: true, }); - const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 }); - const { selectedAssets } = useSelectedAssets(); - const { setContextAction } = useContextActionStore(); - const { setIsRenameMode } = useRenameModeStore(); - const rightDrag = useRef(false); - const isRightMouseDown = useRef(false); + const { gl, controls } = useThree(); + const [canRender, setCanRender] = useState(false); + const [visibility, setVisibility] = useState({ + rename: true, + focus: true, + flipX: true, + flipZ: true, + move: true, + rotate: true, + duplicate: true, + copy: true, + paste: true, + modifier: false, + group: false, + array: false, + delete: true, + }); + const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 }); + const { selectedAssets } = useSelectedAssets(); + const { setContextAction } = useContextActionStore(); + const { setIsRenameMode } = useRenameModeStore(); + const rightDrag = useRef(false); + const isRightMouseDown = useRef(false); - useEffect(() => { - if (selectedAssets.length === 1) { - setVisibility({ - rename: true, - focus: true, - flipX: true, - flipZ: true, - move: true, - rotate: true, - duplicate: true, - copy: true, - paste: true, - modifier: false, - group: false, - array: false, - delete: true, - }); - } else if (selectedAssets.length > 1) { - setVisibility({ - rename: false, - focus: true, - flipX: true, - flipZ: true, - move: true, - rotate: true, - duplicate: true, - copy: true, - paste: true, - modifier: false, - group: true, - array: false, - delete: true, - }); - } else { - setVisibility({ - rename: false, - focus: false, - flipX: false, - flipZ: false, - move: false, - rotate: false, - duplicate: false, - copy: false, - paste: false, - modifier: false, - group: false, - array: false, - delete: false, - }); + useEffect(() => { + if (selectedAssets.length === 1) { + setVisibility({ + rename: true, + focus: true, + flipX: true, + flipZ: true, + move: true, + rotate: true, + duplicate: true, + copy: true, + paste: true, + modifier: false, + group: false, + array: false, + delete: true, + }); + } else if (selectedAssets.length > 1) { + setVisibility({ + rename: false, + focus: true, + flipX: true, + flipZ: true, + move: true, + rotate: true, + duplicate: true, + copy: true, + paste: true, + modifier: false, + group: true, + array: false, + delete: true, + }); + } else { + setVisibility({ + rename: false, + focus: false, + flipX: false, + flipZ: false, + move: false, + rotate: false, + duplicate: false, + copy: false, + paste: false, + modifier: false, + group: false, + array: false, + delete: false, + }); + } + }, [selectedAssets]); + + useEffect(() => { + const canvasElement = gl.domElement; + + const onPointerDown = (evt: any) => { + if (evt.button === 2) { + isRightMouseDown.current = true; + rightDrag.current = false; + } + }; + + const onPointerMove = () => { + if (isRightMouseDown.current) { + rightDrag.current = true; + } + }; + + const onPointerUp = (evt: any) => { + if (evt.button === 2) { + isRightMouseDown.current = false; + } + }; + + const handleContextClick = (event: MouseEvent) => { + event.preventDefault(); + if (rightDrag.current) return; + if (selectedAssets.length > 0) { + setMenuPosition({ + x: event.clientX - gl.domElement.width / 2, + y: event.clientY - gl.domElement.height / 2, + }); + setCanRender(true); + if (controls) { + (controls as CameraControls).enabled = false; } - }, [selectedAssets]); - - useEffect(() => { - const canvasElement = gl.domElement; - - const onPointerDown = (evt: any) => { - if (evt.button === 2) { - isRightMouseDown.current = true; - rightDrag.current = false; - } - }; - - const onPointerMove = () => { - if (isRightMouseDown.current) { - rightDrag.current = true; - } - }; - - const onPointerUp = (evt: any) => { - if (evt.button === 2) { - isRightMouseDown.current = false; - } - }; - - const handleContextClick = (event: MouseEvent) => { - event.preventDefault(); - if (rightDrag.current) return; - if (selectedAssets.length > 0) { - setMenuPosition({ x: event.clientX - gl.domElement.width / 2, y: event.clientY - gl.domElement.height / 2 }); - setCanRender(true); - if (controls) { - (controls as CameraControls).enabled = false; - } - } else { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - } - }; - - if (selectedAssets.length > 0) { - canvasElement.addEventListener('pointerdown', onPointerDown); - canvasElement.addEventListener('pointermove', onPointerMove); - canvasElement.addEventListener('pointerup', onPointerUp); - canvasElement.addEventListener('contextmenu', handleContextClick) - } else { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setMenuPosition({ x: 0, y: 0 }); - } - - return () => { - canvasElement.removeEventListener('pointerdown', onPointerDown); - canvasElement.removeEventListener('pointermove', onPointerMove); - canvasElement.removeEventListener('pointerup', onPointerUp); - canvasElement.removeEventListener('contextmenu', handleContextClick); - }; - }, [gl, selectedAssets]); - - const handleAssetRename = () => { + } else { setCanRender(false); if (controls) { - (controls as CameraControls).enabled = true; + (controls as CameraControls).enabled = true; } - setContextAction("renameAsset"); - setIsRenameMode(true); - } - const handleAssetFocus = () => { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setContextAction("focusAsset"); - } - const handleAssetMove = () => { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setContextAction("moveAsset") - } - const handleAssetRotate = () => { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setContextAction("rotateAsset") - } - const handleAssetCopy = () => { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setContextAction("copyAsset") - } - const handleAssetPaste = () => { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setContextAction("pasteAsset") - } - const handleAssetDelete = () => { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setContextAction("deleteAsset") - } - const handleAssetDuplicate = () => { - setCanRender(false); - if (controls) { - (controls as CameraControls).enabled = true; - } - setContextAction("duplicateAsset") + } + }; + + if (selectedAssets.length > 0) { + canvasElement.addEventListener("pointerdown", onPointerDown); + canvasElement.addEventListener("pointermove", onPointerMove); + canvasElement.addEventListener("pointerup", onPointerUp); + canvasElement.addEventListener("contextmenu", handleContextClick); + } else { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setMenuPosition({ x: 0, y: 0 }); } - return ( - <> - {canRender && ( - - - handleAssetRename()} - onFocus={() => handleAssetFocus()} - onFlipX={() => console.log("Flip to X")} - onFlipZ={() => console.log("Flip to Z")} - onMove={() => handleAssetMove()} - onRotate={() => handleAssetRotate()} - onDuplicate={() => handleAssetDuplicate()} - onCopy={() => handleAssetCopy()} - onPaste={() => handleAssetPaste()} - onGroup={() => console.log("Group")} - onArray={() => console.log("Array")} - onDelete={() => handleAssetDelete()} - /> - - - )} - - ); + return () => { + canvasElement.removeEventListener("pointerdown", onPointerDown); + canvasElement.removeEventListener("pointermove", onPointerMove); + canvasElement.removeEventListener("pointerup", onPointerUp); + canvasElement.removeEventListener("contextmenu", handleContextClick); + }; + }, [controls, gl, selectedAssets]); + + const handleAssetRename = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("renameAsset"); + setIsRenameMode(true); + }; + const handleAssetFocus = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("focusAsset"); + }; + const handleAssetMove = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("moveAsset"); + }; + const handleAssetRotate = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("rotateAsset"); + }; + const handleAssetCopy = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("copyAsset"); + }; + const handleAssetPaste = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("pasteAsset"); + }; + const handleAssetDelete = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("deleteAsset"); + }; + const handleAssetDuplicate = () => { + setCanRender(false); + if (controls) { + (controls as CameraControls).enabled = true; + } + setContextAction("duplicateAsset"); + }; + + return ( + <> + {canRender && ( + + + handleAssetRename()} + onFocus={() => handleAssetFocus()} + onFlipX={() => console.log("Flip to X")} + onFlipZ={() => console.log("Flip to Z")} + onMove={() => handleAssetMove()} + onRotate={() => handleAssetRotate()} + onDuplicate={() => handleAssetDuplicate()} + onCopy={() => handleAssetCopy()} + onPaste={() => handleAssetPaste()} + onGroup={() => console.log("Group")} + onArray={() => console.log("Array")} + onDelete={() => handleAssetDelete()} + /> + + + )} + + ); } export default ContextControls; diff --git a/app/src/modules/scene/tools/measurementTool.tsx b/app/src/modules/scene/tools/measurementTool.tsx index 4ca56fd..9f8189d 100644 --- a/app/src/modules/scene/tools/measurementTool.tsx +++ b/app/src/modules/scene/tools/measurementTool.tsx @@ -1,5 +1,5 @@ import * as THREE from "three"; -import { useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { useThree, useFrame } from "@react-three/fiber"; import { useToolMode } from "../../../store/builder/store"; import { Html, Line } from "@react-three/drei"; @@ -10,7 +10,81 @@ const MeasurementTool = () => { const [points, setPoints] = useState([]); const [linePoints, setLinePoints] = useState(null); + const [axisLock, setAxisLock] = useState<"x" | "y" | "z" | null>(null); const groupRef = useRef(null); + const keysPressed = useRef>(new Set()); + + // Axis lock key handling + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.altKey) { + if (e.key.toLowerCase() === "x") keysPressed.current.add("x"); + else if (e.key.toLowerCase() === "y") keysPressed.current.add("y"); + else if (e.key.toLowerCase() === "z") keysPressed.current.add("z"); + + if (keysPressed.current.has("x")) setAxisLock("x"); + else if (keysPressed.current.has("y")) setAxisLock("y"); + else if (keysPressed.current.has("z")) setAxisLock("z"); + } else if (e.key === "Escape") { + setPoints([]); + setLinePoints(null); + setAxisLock(null); + } + }; + + const handleKeyUp = (e: KeyboardEvent) => { + keysPressed.current.delete(e.key.toLowerCase()); + if (keysPressed.current.has("x")) setAxisLock("x"); + else if (keysPressed.current.has("y")) setAxisLock("y"); + else if (keysPressed.current.has("z")) setAxisLock("z"); + else setAxisLock(null); + }; + + window.addEventListener("keydown", handleKeyDown); + window.addEventListener("keyup", handleKeyUp); + return () => { + window.removeEventListener("keydown", handleKeyDown); + window.removeEventListener("keyup", handleKeyUp); + }; + }, []); + + const getLineColor = useCallback(() => { + switch (axisLock) { + case "x": + return "#d94522"; // Red for X axis + case "y": + return "#22ab2e"; // Green for Y axis + case "z": + return "#227bd9"; // Blue for Z axis + default: + return "#b18ef1"; // Default purple + } + }, [axisLock]); + + // Apply axis lock to a point + const applyAxisLock = useCallback( + (point: THREE.Vector3, referencePoint: THREE.Vector3) => { + const lockedPoint = point.clone(); + + switch (axisLock) { + case "x": + lockedPoint.y = referencePoint.y; + lockedPoint.z = referencePoint.z; + break; + case "y": + lockedPoint.x = referencePoint.x; + lockedPoint.z = referencePoint.z; + break; + case "z": + lockedPoint.x = referencePoint.x; + lockedPoint.y = referencePoint.y; + break; + } + + return lockedPoint; + }, + [axisLock] + ); useEffect(() => { const canvasElement = gl.domElement; @@ -45,7 +119,13 @@ const MeasurementTool = () => { ); if (intersects.length > 0) { - const intersectionPoint = intersects[0].point.clone(); + let intersectionPoint = intersects[0].point.clone(); + if (axisLock && points.length > 0) { + intersectionPoint = applyAxisLock( + intersectionPoint, + points[points.length - 1] + ); + } if (points.length < 2) { setPoints([...points, intersectionPoint]); } else { @@ -84,7 +164,7 @@ const MeasurementTool = () => { canvasElement.removeEventListener("contextmenu", onContextMenu); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [toolMode, camera, raycaster, pointer, scene, points]); + }, [toolMode, camera, raycaster, pointer, scene, points, axisLock]); useFrame(() => { if (points.length === 1) { @@ -107,7 +187,10 @@ const MeasurementTool = () => { ); if (intersects.length > 0) { - const tempEnd = intersects[0].point.clone(); + let tempEnd = intersects[0].point.clone(); + if (axisLock) { + tempEnd = applyAxisLock(tempEnd, points[0]); + } updateMeasurement(points[0], tempEnd); } } else if (points.length === 2) { @@ -139,7 +222,7 @@ const MeasurementTool = () => { {/* Main line */} Date: Mon, 25 Aug 2025 15:56:30 +0530 Subject: [PATCH 09/14] refactor: improve code readability and maintainability in MainScene, Messages, and ThreadChat components; adjust comments.scss for better layout --- .../components/layout/scenes/MainScene.tsx | 6 +++- .../components/ui/collaboration/Messages.tsx | 31 ++++++++++++------- .../ui/collaboration/ThreadChat.tsx | 16 +++++++--- app/src/styles/scene/comments.scss | 4 +-- 4 files changed, 38 insertions(+), 19 deletions(-) diff --git a/app/src/components/layout/scenes/MainScene.tsx b/app/src/components/layout/scenes/MainScene.tsx index f5e0c72..350c9e9 100644 --- a/app/src/components/layout/scenes/MainScene.tsx +++ b/app/src/components/layout/scenes/MainScene.tsx @@ -212,7 +212,11 @@ function MainScene() { {activeModule !== "market" && !selectedUser &&
} - {(commentPositionState !== null || selectedComment !== null) && } + + { + (commentPositionState !== null || selectedComment !== null) && + + } ); diff --git a/app/src/components/ui/collaboration/Messages.tsx b/app/src/components/ui/collaboration/Messages.tsx index b3f37e1..11e87b2 100644 --- a/app/src/components/ui/collaboration/Messages.tsx +++ b/app/src/components/ui/collaboration/Messages.tsx @@ -231,7 +231,10 @@ const Messages: React.FC = ({ val, i, setMessages, mode, setIsEdit
{isEditableThread ? getRelativeTime(val.createdAt) : val.createdAt}
{(val as Reply).creatorId === userId && ( -
+
setOpenOptions(false)} + > + {openOptions && (
- {!(isEditableThread) && } + + {!isEditableThread && ( + + )}
)}
)} +
{"comment" in val ? val.comment : val.threadTitle}
+
)} diff --git a/app/src/components/ui/collaboration/ThreadChat.tsx b/app/src/components/ui/collaboration/ThreadChat.tsx index 8e4b24b..5370689 100644 --- a/app/src/components/ui/collaboration/ThreadChat.tsx +++ b/app/src/components/ui/collaboration/ThreadChat.tsx @@ -131,9 +131,10 @@ const ThreadChat: React.FC = () => { if (dragging) updatePosition(e, true); }; - useEffect(() => { - updatePosition({ clientX: position.x, clientY: position.y }, true); - }, [selectedComment]); + // Commented this useEffect to prevent offset after user saved the comment + // useEffect(() => { + // updatePosition({ clientX: position.x, clientY: position.y }, true); + // }, [selectedComment]); const handlePointerUp = (event: React.PointerEvent) => { @@ -144,6 +145,10 @@ const ThreadChat: React.FC = () => { }; const handleCreateComments = async (e: any) => { + // Continue send or create message only there is only value avalibale + // To prevent empty value + + if (!value) return; e.preventDefault(); try { // const createComments = await addCommentsApi(projectId, value, selectedComment?.threadId, selectedVersion?.versionId || "")/ @@ -163,6 +168,7 @@ const ThreadChat: React.FC = () => { // } + if (threadSocket && mode === "create") { const addComment = { versionId: selectedVersion?.versionId || "", @@ -190,7 +196,7 @@ const ThreadChat: React.FC = () => { // removeComment(selectedComment?.threadId) // setSelectedComment([]) // } - console.log('threadSocket:threadChat ', threadSocket); + if (threadSocket) { // projectId, userId, organization, threadId const deleteThread = { @@ -258,7 +264,7 @@ const ThreadChat: React.FC = () => { }; if (threadSocket) { - console.log('createThread: ', createThread); + setInputActive(false); threadSocket.emit("v1:thread:create", createThread); diff --git a/app/src/styles/scene/comments.scss b/app/src/styles/scene/comments.scss index 480594a..ed4a1be 100644 --- a/app/src/styles/scene/comments.scss +++ b/app/src/styles/scene/comments.scss @@ -171,8 +171,8 @@ .messages-wrapper { padding: 12px; padding-top: 0; - max-height: 50vh; - overflow-y: auto; + max-height: 36vh; + overflow: auto; .edit-container { .input-container { textarea { From 246236c15fe085cdca5fb007a3f04594b69d4d14 Mon Sep 17 00:00:00 2001 From: Vishnu Date: Mon, 25 Aug 2025 16:22:41 +0530 Subject: [PATCH 10/14] fix: - react-hooks/exhaustive-deps added - style update - key fix --- .../layout/sidebarRight/properties/AssetProperties.tsx | 4 ++-- .../selectionControls/selection3D/moveControls3D.tsx | 3 +++ .../selectionControls/selection3D/selectionControls3D.tsx | 5 ++++- app/src/styles/components/form.scss | 1 + 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx index 5d0a97c..a62abda 100644 --- a/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AssetProperties.tsx @@ -78,8 +78,8 @@ const AssetProperties: React.FC = () => {
User Data
- {userData.map((data) => ( -
+ {userData.map((data, i) => ( +
{ @@ -176,6 +177,7 @@ function MoveControls3D({ boundingBoxRef }: any) { canvasElement.removeEventListener("keydown", onKeyDown); canvasElement?.removeEventListener("keyup", onKeyUp); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent, initialStates]); const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => { @@ -223,6 +225,7 @@ function MoveControls3D({ boundingBoxRef }: any) { setDragOffset(newOffset); } } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [axisConstraint, camera, movedObjects]) useFrame(() => { diff --git a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx index 1dab7bc..ed458c2 100644 --- a/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx +++ b/app/src/modules/scene/controls/selectionControls/selection3D/selectionControls3D.tsx @@ -67,6 +67,7 @@ const SelectionControls3D: React.FC = () => { setContextAction(null); deleteSelection() } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [contextAction]) useEffect(() => { @@ -222,12 +223,14 @@ const SelectionControls3D: React.FC = () => { helper.enabled = false; helper.dispose(); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, duplicatedObjects, movedObjects, socket, rotatedObjects, activeModule, toolMode]); useEffect(() => { if (activeModule !== "builder" || toolMode !== 'cursor' || toggleView) { clearSelection(); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [activeModule, toolMode, toggleView]); const selectAssets = useCallback(() => { @@ -362,7 +365,7 @@ const SelectionControls3D: React.FC = () => { removeAsset(uuid); }); - echo.success("Selected models removed!"); + echo.warn("Selected models removed!"); clearSelection(); } }; diff --git a/app/src/styles/components/form.scss b/app/src/styles/components/form.scss index 67accd2..31b9015 100644 --- a/app/src/styles/components/form.scss +++ b/app/src/styles/components/form.scss @@ -9,6 +9,7 @@ border-radius: #{$border-radius-large}; outline: 1px solid var(--border-color); z-index: 100; + backdrop-filter: blur(4px); .header { @include flex-center; gap: 8px; From 6f308ddda4cc364b6dd512fd47be7c8b6112f31a Mon Sep 17 00:00:00 2001 From: Nalvazhuthi Date: Mon, 25 Aug 2025 16:23:21 +0530 Subject: [PATCH 11/14] refactor: improve formatting and readability in ShortcutHelper, DisplayZone, and KeyPressListener components --- app/src/components/footer/shortcutHelper.tsx | 5 ++-- .../visualization/zone/DisplayZone.tsx | 25 ++++++++----------- .../utils/shortcutkeys/handleShortcutKeys.ts | 6 ++++- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/src/components/footer/shortcutHelper.tsx b/app/src/components/footer/shortcutHelper.tsx index afbaa0e..01ada84 100644 --- a/app/src/components/footer/shortcutHelper.tsx +++ b/app/src/components/footer/shortcutHelper.tsx @@ -326,9 +326,8 @@ const ShortcutHelper: React.FC = ({
{activeShortcuts.map((item) => (
= ({ {Object.keys(zonesData).length !== 0 ? ( <> {Object.values(zonesData).map((zone, index) => ( - <> - { } -
{ +
{ - handleSelect2dZoneData(zonesData[zone.zoneUuid]?.zoneUuid, zone.zoneName) - } - } - > - {zone.zoneName} -
- + handleSelect2dZoneData(zonesData[zone.zoneUuid]?.zoneUuid, zone.zoneName) + } + } + > + {zone.zoneName} +
))} ) : ( diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts index 507e31b..6a03563 100644 --- a/app/src/utils/shortcutkeys/handleShortcutKeys.ts +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -221,8 +221,12 @@ const KeyPressListener: React.FC = () => { // Shortcuts specific for sidebar visibility toggle and others specific to sidebar if added handleSidebarShortcuts(keyCombination); + + // Active module selection (builder, simulation, etc.) - handleModuleSwitch(keyCombination); + if (event.location === 0) { // Location 0 = standard keyboard (not numpad) + handleModuleSwitch(keyCombination); + } // Common editing tools: cursor | delete | free-hand handlePrimaryTools(keyCombination); // Shortcuts specific to the builder module (e.g., drawing and measurement tools) From 6aeef163d2359c060592d0096a6f2ed6c063bf3f Mon Sep 17 00:00:00 2001 From: Gomathi9520 Date: Mon, 25 Aug 2025 17:05:24 +0530 Subject: [PATCH 12/14] feat: implement resource management ID handling in Hrm and AssetManagement components --- .../resourceManagement/hrm/Hrm.tsx | 17 +++++++++--- .../hrm/assetManagement/AssetManagement.tsx | 26 ++++++++++++------- app/src/components/ui/list/List.tsx | 15 ++++++----- .../model/eventHandlers/useEventHandlers.ts | 19 +++++++++++--- app/src/store/builder/store.ts | 10 +++++++ 5 files changed, 62 insertions(+), 25 deletions(-) diff --git a/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx b/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx index 369c04f..99828ae 100644 --- a/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx +++ b/app/src/components/layout/sidebarRight/resourceManagement/hrm/Hrm.tsx @@ -3,6 +3,8 @@ import { ClockThreeIcon, LocationPinIcon, TargetIcon } from '../../../../icons/E import { useSceneContext } from '../../../../../modules/scene/sceneContext'; import { useProductContext } from '../../../../../modules/simulation/products/productContext'; import RenameInput from '../../../../ui/inputs/RenameInput'; +import { useResourceManagementId } from '../../../../../store/builder/store'; +import { set } from 'immer/dist/internal'; // import NavigateCatagory from '../NavigateCatagory' const Hrm = () => { @@ -13,6 +15,7 @@ const Hrm = () => { const { products, getProductById } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); + const { setResourceManagementId } = useResourceManagementId(); useEffect(() => { if (selectedProduct) { @@ -25,6 +28,7 @@ const Hrm = () => { employee: { image: "", name: worker.modelName, + modelId: worker.modelUuid, employee_id: `HR-${204 + index}`, status: "Active", }, @@ -54,7 +58,7 @@ const Hrm = () => { }, [selectedProduct, getProductById]); useEffect(() => { - // console.log("Workers data updated:", workers); + // }, [workers]); @@ -147,9 +151,14 @@ const Hrm = () => { // }, // ] function handleRenameWorker(newName: string) { - // console.log('newName: ', newName); + // } + function handleHumanClick(employee: any) { + if (employee.modelId) { + setResourceManagementId(employee.modelId); + } + } return ( @@ -173,14 +182,14 @@ const Hrm = () => {
-
+
{/*
{employee.employee.name}
*/}
{employee.employee.employee_id}
-
View more
+
{ handleHumanClick(employee.employee) }}>View in Scene
diff --git a/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx b/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx index 2cfe731..463afe1 100644 --- a/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx +++ b/app/src/components/layout/sidebarRight/resourceManagement/hrm/assetManagement/AssetManagement.tsx @@ -5,6 +5,7 @@ import assetImage from "../../../../../../assets/image/asset-image.png" import { useSceneContext } from '../../../../../../modules/scene/sceneContext'; import { useProductContext } from '../../../../../../modules/simulation/products/productContext'; import RenameInput from '../../../../../ui/inputs/RenameInput'; +import { useResourceManagementId } from '../../../../../../store/builder/store'; const AssetManagement = () => { // const [selectedCategory, setSelectedCategory] = useState("All Assets"); const [expandedAssetId, setExpandedAssetId] = useState(null); @@ -14,6 +15,7 @@ const AssetManagement = () => { const { products, getProductById } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct } = selectedProductStore(); + const { setResourceManagementId } = useResourceManagementId(); @@ -26,7 +28,7 @@ const AssetManagement = () => { if (asset.type === "storageUnit" || asset.type === "human") return; if (!grouped[asset.modelName]) { grouped[asset.modelName] = { - id: asset.assetId, + id: asset.modelUuid, name: asset.modelName, model: asset.modelCode || "N/A", status: asset.status || "Online", @@ -47,7 +49,7 @@ const AssetManagement = () => { }, [selectedProduct]); function handleRenameAsset(newName: string) { - // console.log('newName: ', newName); + // // if (expandedAssetId) { // setAssets(prevAssets => // prevAssets.map(asset => @@ -62,6 +64,12 @@ const AssetManagement = () => { }, [assets]); + function handleAssetClick(id: string) { + + + setResourceManagementId(id); + } + // const dummyAssets = [ // { // id: '1', @@ -105,10 +113,10 @@ const AssetManagement = () => { return ( <> {/* */} + category={["All Assets", "Machines", "Workstation", "Vehicles"]} + selectedCategory={selectedCategory} + setSelectedCategory={setSelectedCategory} + /> */}
{assets.map((asset, index) => ( @@ -122,15 +130,13 @@ const AssetManagement = () => {
}
-
+
{/*
{asset.name}
*/} {asset.count !== 1 &&
x {asset.count}
} - -
{asset.model}
@@ -163,7 +169,7 @@ const AssetManagement = () => {
setExpandedAssetId(null)}> -
View in Scene
+
handleAssetClick(asset.id)}>View in Scene
diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx index 6792fa4..8f38189 100644 --- a/app/src/components/ui/list/List.tsx +++ b/app/src/components/ui/list/List.tsx @@ -57,6 +57,7 @@ const List: React.FC = ({ items = [], remove }) => { const { zoneStore } = useSceneContext(); const { zones, setZoneName } = zoneStore(); + useEffect(() => { useSelectedZoneStore.getState().setSelectedZone({ zoneName: "", @@ -121,6 +122,7 @@ const List: React.FC = ({ items = [], remove }) => { } function handleAssetClick(asset: Asset) { + setZoneAssetId(asset); } @@ -157,8 +159,9 @@ const List: React.FC = ({ items = [], remove }) => { modelUuid: zoneAssetId.id, modelName: newName, projectId, + versionId: selectedVersion?.versionId || '' + }); - // console.log("response: ", response); setName(zoneAssetId.id, response.modelName); } @@ -255,9 +258,8 @@ const List: React.FC = ({ items = [], remove }) => { }} >
+
{shortcuts.map((group) => ( diff --git a/app/src/hooks/useCameraShortcuts.ts b/app/src/hooks/useCameraShortcuts.ts new file mode 100644 index 0000000..e4b897a --- /dev/null +++ b/app/src/hooks/useCameraShortcuts.ts @@ -0,0 +1,47 @@ +import { useEffect } from "react"; +import { useThree } from "@react-three/fiber"; +import * as THREE from "three"; +import type { CameraControls } from "@react-three/drei"; + +export const useCameraShortcuts = (controlsRef: React.RefObject) => { + const { camera } = useThree(); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (!controlsRef.current) return; + + // get current distance from camera to target + const target = new THREE.Vector3(); + controlsRef.current.getTarget(target); + + const distance = camera.position.distanceTo(target); + let pos: THREE.Vector3 | null = null; + + switch (e.key) { + case "1": // Front + pos = new THREE.Vector3(0, 0, distance).add(target); + break; + case "3": // Right + pos = new THREE.Vector3(distance, 0, 0).add(target); + break; + case "7": // Top + pos = new THREE.Vector3(0, distance, 0).add(target); + break; + case "9": // Back + pos = new THREE.Vector3(0, 0, -distance).add(target); + break; + } + + if (pos) { + controlsRef.current.setLookAt( + pos.x, pos.y, pos.z, // camera position + target.x, target.y, target.z, // keep same target + true // smooth transition + ); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [controlsRef, camera]); +}; diff --git a/app/src/modules/builder/builder.tsx b/app/src/modules/builder/builder.tsx index ab9434b..ef310fc 100644 --- a/app/src/modules/builder/builder.tsx +++ b/app/src/modules/builder/builder.tsx @@ -57,6 +57,7 @@ export default function Builder() { const { setHoveredPoint, setHoveredLine } = useBuilderStore(); const { userId, organization } = getUserData(); + useEffect(() => { if (!toggleView) { setHoveredLine(null); diff --git a/app/src/modules/scene/controls/controls.tsx b/app/src/modules/scene/controls/controls.tsx index c41a4bd..d63f0ac 100644 --- a/app/src/modules/scene/controls/controls.tsx +++ b/app/src/modules/scene/controls/controls.tsx @@ -18,6 +18,7 @@ import ContextControls from "./contextControls/contextControls"; import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D"; import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls"; import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls"; +import { useCameraShortcuts } from "../../../hooks/useCameraShortcuts"; export default function Controls() { const controlsRef = useRef(null); @@ -116,6 +117,7 @@ export default function Controls() { stopInterval(); }; }, [toggleView, state, socket]); + useCameraShortcuts(controlsRef); return ( <> From e813f194c7c19fc3b8c04768f337dc7d999aefca Mon Sep 17 00:00:00 2001 From: Vishnu Date: Mon, 25 Aug 2025 18:21:28 +0530 Subject: [PATCH 14/14] resource manager style update --- app/src/styles/layout/resourceManagement.scss | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/styles/layout/resourceManagement.scss b/app/src/styles/layout/resourceManagement.scss index d62afe8..131220f 100644 --- a/app/src/styles/layout/resourceManagement.scss +++ b/app/src/styles/layout/resourceManagement.scss @@ -105,7 +105,7 @@ header { position: relative; @include flex-space-between; - padding: 3px 0; + padding-bottom: 6px; .user-details { display: flex; @@ -136,6 +136,9 @@ .details { max-width: 144px; + .input-value{ + max-width: 120px; + } .employee-id { color: #b7b7c6; font-size: $tiny;