From 53ade999309259c94cc59b756e262b0bc991592a Mon Sep 17 00:00:00 2001 From: Nalvazhuthi Date: Fri, 22 Aug 2025 18:03:50 +0530 Subject: [PATCH 1/5] 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 2/5] 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 0601c9743faf5611f70a0eb88a7d1167e11bae09 Mon Sep 17 00:00:00 2001 From: Vishnu Date: Mon, 25 Aug 2025 10:40:59 +0530 Subject: [PATCH 3/5] 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 4/5] 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 5/5] 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%); +}