From e589cb22b58f848605e0e0f2812206fc6f8b32cd Mon Sep 17 00:00:00 2001 From: Poovizhi99 Date: Mon, 30 Jun 2025 12:59:16 +0530 Subject: [PATCH 1/4] Refactor: Integrate activeLayer and builder store into SelectFloorPlan and LoadBlueprint components; enhance getWallPointsFromBlueprint function with additional parameters for wall creation; update wall handling in WallCreator and PolygonGenerator components. --- .../components/temporary/SelectFloorPlan.tsx | 12 +- app/src/modules/builder/dfx/LoadBlueprint.tsx | 53 +++--- .../functions/getWallPointsFromBlueprint.ts | 175 ++++++++++++------ .../builder/wall/wallCreator/wallCreator.tsx | 5 + .../vehicle/navMesh/polygonGenerator.tsx | 18 +- .../utils/shortcutkeys/handleShortcutKeys.ts | 13 +- 6 files changed, 174 insertions(+), 102 deletions(-) diff --git a/app/src/components/temporary/SelectFloorPlan.tsx b/app/src/components/temporary/SelectFloorPlan.tsx index 310b2b0..8c47785 100644 --- a/app/src/components/temporary/SelectFloorPlan.tsx +++ b/app/src/components/temporary/SelectFloorPlan.tsx @@ -1,10 +1,12 @@ import React, { useEffect, useState } from "react"; import useLayoutStore from "../../store/builder/uselayoutStore"; -import { useDfxUpload } from "../../store/builder/store"; +import { useActiveLayer, useDfxUpload } from "../../store/builder/store"; import DxfParser from "dxf-parser"; import { getWallPointsFromBlueprint } from "../../modules/builder/dfx/functions/getWallPointsFromBlueprint"; import { convertDXFToThree } from "../../modules/builder/dfx/functions/convertDxfToThree"; import { AIIcon } from "../icons/ExportCommonIcons"; +import { useBuilderStore } from "../../store/builder/useBuilderStore"; +import { useSceneContext } from "../../modules/scene/sceneContext"; type DXFData = any; const SelectFloorPlan: React.FC = () => { // Access layout state and state setters @@ -12,10 +14,13 @@ const SelectFloorPlan: React.FC = () => { // Access DXF-related states and setters const { setDfxUploaded, setDfxGenerate, setObjValue, objValue } = useDfxUpload(); - + const { activeLayer } = useActiveLayer(); + const { wallThickness, wallHeight, insideMaterial, outsideMaterial } = useBuilderStore(); + const { wallStore } = useSceneContext(); + const { addWall } = wallStore(); // Local state to store the parsed DXF file const [parsedFile, setParsedFile] = useState(undefined); - + const { walls } = wallStore(); // Flag to trigger generation after file upload const [generate, setGenerate] = useState(false); @@ -60,6 +65,7 @@ const SelectFloorPlan: React.FC = () => { parsedData: parsedFile, setDfxGenerate, objValue, + wallThickness, wallHeight, outsideMaterial, insideMaterial, activeLayer, addWall, walls }); } }, [generate]); diff --git a/app/src/modules/builder/dfx/LoadBlueprint.tsx b/app/src/modules/builder/dfx/LoadBlueprint.tsx index 1a95268..0a1f3e4 100644 --- a/app/src/modules/builder/dfx/LoadBlueprint.tsx +++ b/app/src/modules/builder/dfx/LoadBlueprint.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef } from 'react'; -import { useDfxUpload, useSocketStore, useToggleView, useUpdateScene } from '../../../store/builder/store'; +import { useActiveLayer, useDfxUpload, useSocketStore, useToggleView, useUpdateScene } from '../../../store/builder/store'; import { LineBasicMaterial, Line } from 'three'; import { TransformControls } from '@react-three/drei'; import { getWallPointsFromBlueprint } from './functions/getWallPointsFromBlueprint'; @@ -7,6 +7,8 @@ import * as Types from '../../../types/world/worldTypes'; import { useParams } from 'react-router-dom'; import { getUserData } from '../../../functions/getUserData'; import { useVersionContext } from '../version/versionContext'; +import { useBuilderStore } from '../../../store/builder/useBuilderStore'; +import { useSceneContext } from '../../scene/sceneContext'; /** * DxfFile component handles the rendering and manipulation of DXf file data in a 3D scene. @@ -22,41 +24,37 @@ const DxfFile = () => { const { selectedVersion } = selectedVersionStore(); const { projectId } = useParams(); const { userId, organization } = getUserData(); - + const { activeLayer } = useActiveLayer(); + const { wallThickness, wallHeight, insideMaterial, outsideMaterial, } = useBuilderStore(); // Refs for storing line objects const lineRefs = useRef([]); - + const { wallStore } = useSceneContext(); + const { addWall, } = wallStore(); + const { walls } = wallStore(); + /** * Effect hook that runs when DXF wall generation is triggered. * Loads initial points and lines from the DXF data and updates the scene. */ useEffect(() => { + if (dfxWallGenerate) { - // Store generated lines in ref - // lines.current.push(...dfxWallGenerate); - // dfxWallGenerate.map((line: any) => { - // const lineData = arrayLineToObject(line as Types.Line); + dfxWallGenerate.map((wall: Wall) => { + const data = { + wallData: wall, + projectId: projectId, + versionId: selectedVersion?.versionId || '', + userId: userId, + organization: organization + } + addWall(wall); + socket.emit('v1:model-Wall:add', data); + // API - // //REST - - // // setLine(organization, lineData.layer!, lineData.line!, lineData.type!); - - // //SOCKET - - // const input = { - // organization, - // layer: lineData.layer, - // line: lineData.line, - // type: lineData.type, - // socketId: socket.id, - // versionId: selectedVersion?.versionId || '', - // projectId, - // userId - // } - - // socket.emit('v1:Line:create', input); - - // }) + // if (projectId) { + // upsertWallApi(projectId, selectedVersion?.versionId || '', wall); + // } + }) } }, [dfxWallGenerate]); @@ -79,6 +77,7 @@ const DxfFile = () => { getWallPointsFromBlueprint({ objValue: { x: position.x, y: position.y, z: position.z }, setDfxGenerate: () => { }, + wallThickness, wallHeight, outsideMaterial, insideMaterial, activeLayer, addWall, walls }); }; diff --git a/app/src/modules/builder/dfx/functions/getWallPointsFromBlueprint.ts b/app/src/modules/builder/dfx/functions/getWallPointsFromBlueprint.ts index a1a5e45..1050a8c 100644 --- a/app/src/modules/builder/dfx/functions/getWallPointsFromBlueprint.ts +++ b/app/src/modules/builder/dfx/functions/getWallPointsFromBlueprint.ts @@ -1,11 +1,21 @@ import { MathUtils, Vector3, BufferGeometry } from "three"; +import { useActiveLayer } from "../../../../store/builder/store"; +import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; +import { wait } from "@testing-library/user-event/dist/utils"; type DXFData = any; // Replace with actual DXF data type type DXFEntity = any; // Replace with actual DXF entity type type WallLineVertex = [Vector3, string, number, string]; // Represents a wall segment with start point, ID, weight, and type interface Props { parsedData?: DXFData; // Parsed DXF file data - setDfxGenerate?: (walls: WallLineVertex[][]) => void; // Callback to set generated walls + setDfxGenerate?: any; // Callback to set generated walls objValue: any; // Object position values for offset calculation + wallThickness: number; + wallHeight: number; + outsideMaterial: string; + insideMaterial: string; + activeLayer: number; // Active layer for wall points + addWall: (wall: Wall) => void; // Function to add a wall to the scene + walls: Wall[]; // Array of walls to be processed } /** @@ -22,14 +32,20 @@ export function getWallPointsFromBlueprint({ parsedData, setDfxGenerate, objValue, + wallThickness, + wallHeight, + outsideMaterial, + insideMaterial, + activeLayer, + addWall, + walls, }: Props) { // Early return if no data is provided if (!parsedData) return; if (!parsedData.entities) return; const unit = 1000; // Conversion factor from millimeters to meters - const wallVertex: WallLineVertex[][] = []; // Stores all generated wall segments - + const wallVertex: any[] = []; // Array to store wall vertices // Process each entity in the DXF file parsedData.entities.forEach((entity: DXFEntity) => { // Handle LINE entities @@ -47,31 +63,39 @@ export function getWallPointsFromBlueprint({ -entity.vertices[1].y / unit ).add(new Vector3(objValue.x, 0, objValue.z)); - // Check if points already exist to avoid duplicates - const existingStart = wallVertex - .flat() - .find((v) => v[0].equals(startVec)); - const startPoint: WallLineVertex = existingStart || [ - startVec, - MathUtils.generateUUID(), // Generate unique ID for new points - 1, // Default weight - "WallLine", // Type identifier - ]; + // Create start and end points + const startPoint: Point = { + pointUuid: MathUtils.generateUUID(), // Generate unique ID for new points + pointType: "Wall", // Type identifier + position: [startVec.x, startVec.y, startVec.z], // Position in 3D space + layer: activeLayer, // Default weight + }; - const existingEnd = wallVertex.flat().find((v) => v[0].equals(endVec)); - const endPoint: WallLineVertex = existingEnd || [ - endVec, - MathUtils.generateUUID(), - 1, - "WallLine", - ]; + const endPoint: Point = { + pointUuid: MathUtils.generateUUID(), // Generate unique ID for new points + pointType: "Wall", // Type identifier + position: [endVec.x, endVec.y, endVec.z], // Position in 3D space + layer: activeLayer, + }; - // Add the line segment to our collection - wallVertex.push([startPoint, endPoint]); + // Create the wall + const wallSet: Wall = { + wallUuid: MathUtils.generateUUID(), + points: [startPoint, endPoint], // Store start and end points + outsideMaterial: insideMaterial, + insideMaterial: outsideMaterial, + wallThickness: wallThickness, + wallHeight: wallHeight, + decals: [], + }; + wallVertex.push(wallSet); // Store wall segment in array + // Add the wall to the scene + // addWall(wallSet); } + // Handle LWPOLYLINE entities (connected line segments) else if (entity.type === "LWPOLYLINE" && entity.vertices) { - let firstPoint: WallLineVertex | undefined; // Store first point for closing the polyline + let firstPoint: Point | undefined; // Store first point for closing the polyline // Process each vertex pair in the polyline for (let i = 0; i < entity.vertices.length - 1; i++) { @@ -88,37 +112,58 @@ export function getWallPointsFromBlueprint({ -entity.vertices[i + 1].y / unit ).add(new Vector3(objValue.x, 0, objValue.z)); - // Check for existing points - const existingStart = wallVertex - .flat() - .find((v) => v[0].equals(startVec)); - const startPoint: WallLineVertex = existingStart || [ - startVec, - MathUtils.generateUUID(), - 1, - "WallLine", - ]; + // Create start and end points + const startPoint: Point = { + pointUuid: MathUtils.generateUUID(), // Generate unique ID for new points + pointType: "Wall", // Type identifier + position: [startVec.x, startVec.y, startVec.z], // Position in 3D space + layer: activeLayer, + }; - const existingEnd = wallVertex.flat().find((v) => v[0].equals(endVec)); - const endPoint: WallLineVertex = existingEnd || [ - endVec, - MathUtils.generateUUID(), - 1, - "WallLine", - ]; + const endPoint: Point = { + pointUuid: MathUtils.generateUUID(), // Generate unique ID for new points + pointType: "Wall", // Type identifier + position: [endVec.x, endVec.y, endVec.z], // Position in 3D space + layer: activeLayer, + }; - wallVertex.push([startPoint, endPoint]); + // Create the wall segment + const wallSet: Wall = { + wallUuid: MathUtils.generateUUID(), + points: [startPoint, endPoint], // Store start and end points + outsideMaterial: insideMaterial, + insideMaterial: outsideMaterial, + wallThickness: wallThickness, + wallHeight: wallHeight, + decals: [], + }; + // Add the wall segment + // addWall(wallSet); + wallVertex.push(wallSet); // Store first point and create closing segment if this is the last vertex if (i === 0) firstPoint = startPoint; if (i === entity.vertices.length - 2 && firstPoint) { - wallVertex.push([endPoint, firstPoint]); + const closingWallSet: Wall = { + wallUuid: MathUtils.generateUUID(), + points: [endPoint, firstPoint], // Create closing segment + outsideMaterial: insideMaterial, + insideMaterial: outsideMaterial, + wallThickness: wallThickness, + wallHeight: wallHeight, + decals: [], + }; + // Add the closing wall + wallVertex.push(closingWallSet); + // addWall(closingWallSet); } } } + // Handle ARC entities else if (entity.type === "ARC") { const { center, radius, startAngle, endAngle } = entity; + // Validate required ARC properties if ( !center || @@ -154,28 +199,38 @@ export function getWallPointsFromBlueprint({ const startVec = arcPoints[i]; const endVec = arcPoints[i + 1]; - // Check for existing points - const existingStart = wallVertex - .flat() - .find((v) => v[0].equals(startVec)); - const startPoint: WallLineVertex = existingStart || [ - startVec, - MathUtils.generateUUID(), - 1, - "WallLine", - ]; + // Create start and end points + const startPoint: Point = { + pointUuid: MathUtils.generateUUID(), + pointType: "Wall", + position: [startVec.x, startVec.y, startVec.z], + layer: activeLayer, + }; - const existingEnd = wallVertex.flat().find((v) => v[0].equals(endVec)); - const endPoint: WallLineVertex = existingEnd || [ - endVec, - MathUtils.generateUUID(), - 1, - "WallLine", - ]; + const endPoint: Point = { + pointUuid: MathUtils.generateUUID(), + pointType: "Wall", + position: [endVec.x, endVec.y, endVec.z], + layer: activeLayer, + }; - wallVertex.push([startPoint, endPoint]); + // Create the wall segment + const wallSet: Wall = { + wallUuid: MathUtils.generateUUID(), + points: [startPoint, endPoint], + outsideMaterial: insideMaterial, + insideMaterial: outsideMaterial, + wallThickness: wallThickness, + wallHeight: wallHeight, + decals: [], + }; + + // Add the wall segment + // addWall(wallSet); + wallVertex.push(wallSet); } } + // Log unsupported entity types else { console.error("Unsupported entity type:", entity.type); diff --git a/app/src/modules/builder/wall/wallCreator/wallCreator.tsx b/app/src/modules/builder/wall/wallCreator/wallCreator.tsx index a80a62d..24a2f95 100644 --- a/app/src/modules/builder/wall/wallCreator/wallCreator.tsx +++ b/app/src/modules/builder/wall/wallCreator/wallCreator.tsx @@ -71,6 +71,7 @@ function WallCreator() { if (wallIntersect && !pointIntersects) { const wall = getWallByPoints(wallIntersect.object.userData.points); + console.log('wall: ', wall); if (wall) { const ThroughPoint = wallIntersect.object.userData.path.getPoints(Constants.lineConfig.lineIntersectionPoints); let intersectionPoint = getClosestIntersection(ThroughPoint, wallIntersect.point); @@ -294,7 +295,9 @@ function WallCreator() { position: [position.x, position.y, position.z], layer: activeLayer }; + console.log('newPoint: ', newPoint); + console.log('snappedPoint: ', snappedPoint); if (snappedPosition && snappedPoint) { newPoint.pointUuid = snappedPoint.pointUuid; newPoint.position = snappedPosition; @@ -316,6 +319,7 @@ function WallCreator() { } } + console.log('tempPoints: ', tempPoints); if (tempPoints.length === 0) { setTempPoints([newPoint]); setIsCreating(true); @@ -330,6 +334,7 @@ function WallCreator() { decals: [] }; addWall(wall); + console.log('wall: ', wall); // API diff --git a/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx b/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx index 34d9f26..f22cef4 100644 --- a/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx +++ b/app/src/modules/simulation/vehicle/navMesh/polygonGenerator.tsx @@ -12,13 +12,12 @@ interface PolygonGeneratorProps { export default function PolygonGenerator({ groupRef, }: PolygonGeneratorProps) { - const { aisleStore } = useSceneContext(); + const { aisleStore, wallStore } = useSceneContext(); const { aisles } = aisleStore(); const { scene } = useThree(); + const { walls } = wallStore(); useEffect(() => { - // let allLines = arrayLinesToObject(lines.current); - // const wallLines = allLines?.filter((line) => line?.type === "WallLine"); const result = aisles .filter( (aisle) => @@ -61,9 +60,12 @@ export default function PolygonGenerator({ }); }); - // const wallPoints = wallLines - // .map((pair) => pair?.line.map((vals) => vals.position)) - // .filter((wall): wall is THREE.Vector3[] => !!wall); + + const wallPoints: THREE.Vector3[][] = walls + .map((wall) => + wall.points.map((pt) => new THREE.Vector3(pt.position[0], pt.position[1], pt.position[2])) + ) + .filter(points => points.length === 2); if (!result || result.some((line) => !line)) { @@ -81,7 +83,7 @@ export default function PolygonGenerator({ const polygons = turf.polygonize(turf.featureCollection(validLineFeatures)); - // renderWallGeometry(wallPoints); + renderWallGeometry(wallPoints); if (polygons.features.length > 0) { polygons.features.forEach((feature) => { @@ -116,7 +118,7 @@ export default function PolygonGenerator({ }); } - }, [ aisles, scene]); + }, [aisles, scene, walls]); const renderWallGeometry = (walls: THREE.Vector3[][]) => { walls.forEach((wall) => { diff --git a/app/src/utils/shortcutkeys/handleShortcutKeys.ts b/app/src/utils/shortcutkeys/handleShortcutKeys.ts index 7090eaa..1f84558 100644 --- a/app/src/utils/shortcutkeys/handleShortcutKeys.ts +++ b/app/src/utils/shortcutkeys/handleShortcutKeys.ts @@ -1,10 +1,14 @@ import React, { useEffect } from "react"; -import useModuleStore, { useSubModuleStore, useThreeDStore } from "../../store/useModuleStore"; +import useModuleStore, { + useSubModuleStore, + useThreeDStore, +} from "../../store/useModuleStore"; import { usePlayerStore, useToggleStore } from "../../store/useUIToggleStore"; import useVersionHistoryVisibleStore, { useActiveSubTool, useActiveTool, useAddAction, + useDfxUpload, useRenameModeStore, useSaveVersion, useSelectedComment, @@ -49,7 +53,7 @@ const KeyPressListener: React.FC = () => { const { setCreateNewVersion } = useVersionHistoryStore(); const { setVersionHistoryVisible } = useVersionHistoryVisibleStore(); const { setSelectedComment } = useSelectedComment(); - + const { setDfxUploaded, setDfxGenerate } = useDfxUpload(); const isTextInput = (element: Element | null): boolean => element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || @@ -160,14 +164,14 @@ const KeyPressListener: React.FC = () => { setCreateNewVersion(true); setVersionHistoryVisible(true); setSubModule("properties"); - setActiveModule('builder'); + setActiveModule("builder"); break; case "Ctrl+H": if (!isPlaying) { setVersionHistoryVisible(true); setSubModule("properties"); - setActiveModule('builder'); + setActiveModule("builder"); } break; @@ -195,6 +199,7 @@ const KeyPressListener: React.FC = () => { clearComparisonProduct(); setIsLogListVisible(false); setIsRenameMode(false); + setDfxUploaded([]); setSelectedComment(null); } From 3a70a13c94a7f0b5f08911f679c871e820a04dc8 Mon Sep 17 00:00:00 2001 From: Poovizhi99 Date: Mon, 30 Jun 2025 14:02:44 +0530 Subject: [PATCH 2/4] Refactor: Add setDfxUploaded to useDfxUpload hook and update useEffect to reset uploaded data on toggle view change --- app/src/modules/builder/dfx/LoadBlueprint.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/modules/builder/dfx/LoadBlueprint.tsx b/app/src/modules/builder/dfx/LoadBlueprint.tsx index 0a1f3e4..bbd4ef9 100644 --- a/app/src/modules/builder/dfx/LoadBlueprint.tsx +++ b/app/src/modules/builder/dfx/LoadBlueprint.tsx @@ -17,7 +17,7 @@ import { useSceneContext } from '../../scene/sceneContext'; */ const DxfFile = () => { // State management hooks - const { dfxuploaded, dfxWallGenerate, setObjValue, objValue } = useDfxUpload(); + const { dfxuploaded, dfxWallGenerate, setObjValue, objValue, setDfxUploaded } = useDfxUpload(); const { toggleView } = useToggleView(); const { socket } = useSocketStore(); const { selectedVersionStore } = useVersionContext(); @@ -31,13 +31,13 @@ const DxfFile = () => { const { wallStore } = useSceneContext(); const { addWall, } = wallStore(); const { walls } = wallStore(); - + /** * Effect hook that runs when DXF wall generation is triggered. * Loads initial points and lines from the DXF data and updates the scene. */ useEffect(() => { - + if (dfxWallGenerate) { dfxWallGenerate.map((wall: Wall) => { const data = { @@ -80,7 +80,11 @@ const DxfFile = () => { wallThickness, wallHeight, outsideMaterial, insideMaterial, activeLayer, addWall, walls }); }; - + useEffect(() => { + if (!toggleView) { + setDfxUploaded([]) + } + }, [toggleView]) return ( <> {/* Render DXF lines with transform controls when DXF data is available and view is toggled */} From 84101905ff2af674157f1c083d2e0b12400fbf78 Mon Sep 17 00:00:00 2001 From: Poovizhi99 Date: Mon, 30 Jun 2025 14:22:02 +0530 Subject: [PATCH 3/4] Refactor: Enhance CollaborationPopup and MultiEmailInvite components; integrate user access sharing and project user retrieval functionalities; update user data structure for consistency. --- .../templates/CollaborationPopup.tsx | 79 ++++++----- .../components/ui/inputs/MultiEmailInvite.tsx | 131 +++++++++++++----- .../collab/getProjectSharedList.ts | 32 +++++ .../factoryBuilder/collab/getSearchUsers.ts | 32 +++++ .../factoryBuilder/collab/shareAccess.ts | 48 +++++++ .../factoryBuilder/collab/shareProject.ts | 36 +++++ app/src/types/users.d.ts | 29 ++-- 7 files changed, 303 insertions(+), 84 deletions(-) create mode 100644 app/src/services/factoryBuilder/collab/getProjectSharedList.ts create mode 100644 app/src/services/factoryBuilder/collab/getSearchUsers.ts create mode 100644 app/src/services/factoryBuilder/collab/shareAccess.ts create mode 100644 app/src/services/factoryBuilder/collab/shareProject.ts diff --git a/app/src/components/templates/CollaborationPopup.tsx b/app/src/components/templates/CollaborationPopup.tsx index c3c81ae..4b196b7 100644 --- a/app/src/components/templates/CollaborationPopup.tsx +++ b/app/src/components/templates/CollaborationPopup.tsx @@ -7,15 +7,22 @@ import { access } from "fs"; import MultiEmailInvite from "../ui/inputs/MultiEmailInvite"; import { useActiveUsers } from "../../store/builder/store"; import { getUserData } from "../../functions/getUserData"; +import { getProjectSharedList } from "../../services/factoryBuilder/collab/getProjectSharedList"; +import { useParams } from "react-router-dom"; +import { shareAccess } from "../../services/factoryBuilder/collab/shareAccess"; +import { getAvatarColor } from "../../modules/collaboration/functions/getAvatarColor"; interface UserListTemplateProps { user: User; } const UserListTemplate: React.FC = ({ user }) => { - const [accessSelection, setAccessSelection] = useState(user.access); + const [accessSelection, setAccessSelection] = useState(user?.Access); + const { projectId } = useParams(); - function accessUpdate({ option }: AccessOption) { + const accessUpdate = async ({ option }: AccessOption) => { + if (!projectId) return + const accessSelection = await shareAccess(projectId, user.userId, option) setAccessSelection(option); } @@ -26,18 +33,22 @@ const UserListTemplate: React.FC = ({ user }) => { {user.profileImage ? ( {`${user.name}'s ) : (
- {user.name[0]} + {user. + userName.charAt(0).toUpperCase()}
)} -
{user.name}
+
{user. + userName.charAt(0).toUpperCase() + user. + userName.slice(1).toLowerCase()}
= ({ }) => { const { activeUsers } = useActiveUsers(); const { userName } = getUserData(); + const [users, setUsers] = useState([]) + const { projectId } = useParams(); + + function getData() { + if (!projectId) return; + getProjectSharedList(projectId).then((allUser) => { + const accesMail = allUser?.datas || [] + console.log('accesMail: ', accesMail); + setUsers(accesMail) + }).catch((err) => { + + }) + } + useEffect(() => { - // console.log("activeUsers: ", activeUsers); + getData(); + }, []) + + useEffect(() => { + // }, [activeUsers]); - const users = [ - { - name: "Alice Johnson", - email: "alice.johnson@example.com", - profileImage: "", - color: "#FF6600", - access: "Admin", - }, - { - name: "Bob Smith", - email: "bob.smith@example.com", - profileImage: "", - color: "#488EF6", - access: "Viewer", - }, - { - name: "Charlie Brown", - email: "charlie.brown@example.com", - profileImage: "", - color: "#48AC2A", - access: "Viewer", - }, - { - name: "Diana Prince", - email: "diana.prince@example.com", - profileImage: "", - color: "#D44242", - access: "Viewer", - }, - ]; return (
= ({
Share this file
-
copy link
+ {/*
copy link
*/}
{ @@ -124,7 +123,7 @@ const CollaborationPopup: React.FC = ({
- +
@@ -142,7 +141,7 @@ const CollaborationPopup: React.FC = ({
{userName && userName[0].toUpperCase()}
- {userName} + {userName && userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase()}
you
diff --git a/app/src/components/ui/inputs/MultiEmailInvite.tsx b/app/src/components/ui/inputs/MultiEmailInvite.tsx index a16de66..4a3bbf3 100644 --- a/app/src/components/ui/inputs/MultiEmailInvite.tsx +++ b/app/src/components/ui/inputs/MultiEmailInvite.tsx @@ -1,72 +1,135 @@ import React, { useState } from "react"; +import { getSearchUsers } from "../../../services/factoryBuilder/collab/getSearchUsers"; +import { useParams } from "react-router-dom"; +import { shareProject } from "../../../services/factoryBuilder/collab/shareProject"; +import { getUserData } from "../../../functions/getUserData"; -const MultiEmailInvite: React.FC = () => { - const [emails, setEmails] = useState([]); +interface UserData { + _id: string; + Email: string; + userName: string; +} + +interface MultiEmailProps { + users: any, + getData: any, +} +const MultiEmailInvite: React.FC = ({ users, getData }) => { + const [emails, setEmails] = useState([]); + const [searchedEmail, setSearchedEmail] = useState([]); + const [inputFocus, setInputFocus] = useState(false); const [inputValue, setInputValue] = useState(""); + const { projectId } = useParams(); + const { userId } = getUserData(); - const handleAddEmail = () => { + const handleAddEmail = async (selectedUser: UserData) => { + if (!projectId || !selectedUser) return const trimmedEmail = inputValue.trim(); + setEmails((prev: any[]) => { + if (!selectedUser) return prev; + const isNotCurrentUser = selectedUser._id !== userId; + const alreadyExistsInEmails = prev.some(email => email._id === selectedUser._id); + const alreadyExistsInUsers = users.some((val: any) => val.userId === selectedUser._id); + if (isNotCurrentUser && !alreadyExistsInEmails && !alreadyExistsInUsers) { + return [...prev, selectedUser]; + } - // Validate email - if (!trimmedEmail || !validateEmail(trimmedEmail)) { - alert("Please enter a valid email address."); - return; - } - - // Check for duplicates - if (emails.includes(trimmedEmail)) { - alert("This email has already been added."); - return; - } - - // Add email to the list - setEmails([...emails, trimmedEmail]); + return prev; + }); setInputValue(""); // Clear the input field after adding }; + const handleSearchMail = async (e: any) => { + setInputValue(e.target.value); + const trimmedEmail = e.target.value.trim(); - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Enter" || e.key === ",") { - e.preventDefault(); - handleAddEmail(); + if (trimmedEmail.length < 3) return; + try { + const searchedMail = await getSearchUsers(trimmedEmail); + const filteredEmail = searchedMail.sharchMail?.filtered; + if (filteredEmail) { + setSearchedEmail(filteredEmail) + } + } catch (error) { + console.error("Failed to search mail:", error); } }; - const handleRemoveEmail = (emailToRemove: string) => { - setEmails(emails.filter((email) => email !== emailToRemove)); + + const handleKeyDown = async (e: React.KeyboardEvent) => { + if (e.key === "Enter" || e.key === "," && searchedEmail.length > 0) { + e.preventDefault(); + handleAddEmail(searchedEmail[0]); + } }; + const handleRemoveEmail = (idToRemove: string) => { + setEmails((prev: any) => prev.filter((email: any) => email._id !== idToRemove)); + }; + + const validateEmail = (email: string) => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }; - const [inputFocus, setInputFocus] = useState(false); + const handleInvite = () => { + if (!projectId) return; + try { + emails.forEach((user: any) => { + shareProject(user._id, projectId) + .then((res) => { + console.log("sharedProject:", res); + + }) + .catch((err) => { + console.error("Error sharing project:", err); + }); + setEmails([]) + setInputValue("") + }); + setTimeout(() => { + getData() + }, 1000); + } catch (error) { + console.error("General error:", error); + } + }; return (
- {emails.map((email, index) => ( + {emails.map((email: any, index: number) => (
- {email} - handleRemoveEmail(email)}>× + {email.Email} + handleRemoveEmail(email._id)}>×
))} setInputValue(e.target.value)} + onChange={(e) => handleSearchMail(e)} onFocus={() => setInputFocus(true)} - onBlur={() => setInputFocus(false)} + // onBlur={() => setInputFocus(false)} onKeyDown={handleKeyDown} placeholder="Enter email and press Enter or comma to seperate" />
-
- Invite -
-
- {/* list available users */} +
+ Add
+ {inputFocus && inputValue.length > 2 && searchedEmail && searchedEmail.length > 0 && ( +
+ {/* list available users here */} + {searchedEmail.map((val: any, i: any) => ( +
{ + handleAddEmail(val) + setInputFocus(false) + }} key={i} > + {val?.Email} +
+ ))} +
+ )}
); }; diff --git a/app/src/services/factoryBuilder/collab/getProjectSharedList.ts b/app/src/services/factoryBuilder/collab/getProjectSharedList.ts new file mode 100644 index 0000000..0c39b99 --- /dev/null +++ b/app/src/services/factoryBuilder/collab/getProjectSharedList.ts @@ -0,0 +1,32 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const getProjectSharedList = async (projectId: string) => { + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/V1/projectsharedList?projectId=${projectId}`, + { + method: "GET", + headers: { + Authorization: "Bearer ", + "Content-Type": "application/json", + token: localStorage.getItem("token") || "", + refresh_token: localStorage.getItem("refreshToken") || "", + }, + } + ); + const newAccessToken = response.headers.get("x-access-token"); + if (newAccessToken) { + //console.log("New token received:", newAccessToken); + localStorage.setItem("token", newAccessToken); + } + + if (!response.ok) { + throw new Error("Failed to get users"); + } + + return await response.json(); + } catch (error: any) { + echo.error("Failed to get users"); + console.log(error.message); + } +}; diff --git a/app/src/services/factoryBuilder/collab/getSearchUsers.ts b/app/src/services/factoryBuilder/collab/getSearchUsers.ts new file mode 100644 index 0000000..01ab7a9 --- /dev/null +++ b/app/src/services/factoryBuilder/collab/getSearchUsers.ts @@ -0,0 +1,32 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const getSearchUsers = async (searchMail: string) => { + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/V1/searchMail?searchMail=${searchMail}`, + { + method: "GET", + headers: { + Authorization: "Bearer ", + "Content-Type": "application/json", + token: localStorage.getItem("token") || "", + refresh_token: localStorage.getItem("refreshToken") || "", + }, + } + ); + const newAccessToken = response.headers.get("x-access-token"); + if (newAccessToken) { + //console.log("New token received:", newAccessToken); + localStorage.setItem("token", newAccessToken); + } + + if (!response.ok) { + throw new Error("Failed to get users"); + } + + return await response.json(); + } catch (error: any) { + echo.error("Failed to get users"); + console.log(error.message); + } +}; diff --git a/app/src/services/factoryBuilder/collab/shareAccess.ts b/app/src/services/factoryBuilder/collab/shareAccess.ts new file mode 100644 index 0000000..de7a824 --- /dev/null +++ b/app/src/services/factoryBuilder/collab/shareAccess.ts @@ -0,0 +1,48 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; +// let url_Backend_dwinzo = `http://192.168.0.102:5000`; + +export const shareAccess = async ( + projectId: string, + targetUserId: string, + newAccessPoint: string +) => { + const body: any = { + projectId, + targetUserId, + newAccessPoint, + }; + + try { + const response = await fetch( + `${url_Backend_dwinzo}/api/V1/sharedAccespoint`, + { + method: "PATCH", + headers: { + Authorization: "Bearer ", + "Content-Type": "application/json", + token: localStorage.getItem("token") || "", + refresh_token: localStorage.getItem("refreshToken") || "", + }, + body: JSON.stringify(body), + } + ); + const newAccessToken = response.headers.get("x-access-token"); + if (newAccessToken) { + //console.log("New token received:", newAccessToken); + localStorage.setItem("token", newAccessToken); + } + + if (!response.ok) { + console.error("Failed to clearPanel in the zone"); + } + + const result = await response.json(); + return result; + } catch (error) { + if (error instanceof Error) { + console.error(error.message); + } else { + console.error("An unknown error occurred"); + } + } +}; diff --git a/app/src/services/factoryBuilder/collab/shareProject.ts b/app/src/services/factoryBuilder/collab/shareProject.ts new file mode 100644 index 0000000..eb14ffe --- /dev/null +++ b/app/src/services/factoryBuilder/collab/shareProject.ts @@ -0,0 +1,36 @@ +let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`; + +export const shareProject = async (addUserId: string, projectId: string) => { + try { + const response = await fetch(`${url_Backend_dwinzo}/api/V1/projectshared`, { + method: "POST", + headers: { + Authorization: "Bearer ", + "Content-Type": "application/json", + token: localStorage.getItem("token") || "", + refresh_token: localStorage.getItem("refreshToken") || "", + }, + body: JSON.stringify({ addUserId, projectId }), + }); + const newAccessToken = response.headers.get("x-access-token"); + if (newAccessToken) { + //console.log("New token received:", newAccessToken); + localStorage.setItem("token", newAccessToken); + } + + if (!response.ok) { + console.error("Failed to add project"); + } + + const result = await response.json(); + console.log("result: ", result); + + return result; + } catch (error) { + if (error instanceof Error) { + console.log(error.message); + } else { + console.log("An unknown error occurred"); + } + } +}; diff --git a/app/src/types/users.d.ts b/app/src/types/users.d.ts index 02bc3c5..2267570 100644 --- a/app/src/types/users.d.ts +++ b/app/src/types/users.d.ts @@ -1,13 +1,22 @@ export interface User { - name: string; - email: string; - profileImage: string; - color: string; - access: string; + userName: string; + Email: string; + Access: string; + userId: string; + profileImage?: string; + color?: string; + } +// export interface User { +// name: string; +// email: string; +// profileImage: string; +// color: string; +// access: string; +// } type AccessOption = { - option: string; + option: string; }; export type ActiveUser = { @@ -15,7 +24,7 @@ export type ActiveUser = { userName: string; email: string; activeStatus?: string; // Optional property - position?: { x: number; y: number; z: number; }; - rotation?: { x: number; y: number; z: number; }; - target?: { x: number; y: number; z: number; }; -}; \ No newline at end of file + position?: { x: number; y: number; z: number }; + rotation?: { x: number; y: number; z: number }; + target?: { x: number; y: number; z: number }; +}; From 898179c2c1b8962a3f3054cd0d650c2b6f533d89 Mon Sep 17 00:00:00 2001 From: Poovizhi99 Date: Mon, 30 Jun 2025 15:54:41 +0530 Subject: [PATCH 4/4] Refactor: Update project handling and sharing functionalities; integrate shared projects retrieval; enhance user data structure and loading mechanisms; improve input handling in MultiEmailInvite component; adjust wall generation callbacks in DXF processing. --- .../components/Dashboard/DashboardCard.tsx | 85 +++++----- .../components/Dashboard/DashboardHome.tsx | 8 +- .../Dashboard/DashboardProjects.tsx | 86 +++++++---- .../Dashboard/socket/projectSocketRes.dev.tsx | 16 +- app/src/components/templates/LoadingPage.tsx | 37 +++-- .../components/temporary/SelectFloorPlan.tsx | 4 +- .../components/ui/inputs/MultiEmailInvite.tsx | 146 +++++++++--------- app/src/modules/builder/dfx/LoadBlueprint.tsx | 2 +- .../functions/getWallPointsFromBlueprint.ts | 54 ++++--- app/src/pages/Project.tsx | 32 ++-- .../services/dashboard/sharedWithMeProject.ts | 30 ++++ app/src/store/builder/store.ts | 2 +- app/src/styles/components/input.scss | 19 ++- .../utils/shortcutkeys/handleShortcutKeys.ts | 2 +- 14 files changed, 337 insertions(+), 186 deletions(-) create mode 100644 app/src/services/dashboard/sharedWithMeProject.ts diff --git a/app/src/components/Dashboard/DashboardCard.tsx b/app/src/components/Dashboard/DashboardCard.tsx index 47fa261..68d91e7 100644 --- a/app/src/components/Dashboard/DashboardCard.tsx +++ b/app/src/components/Dashboard/DashboardCard.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef, useEffect } from "react"; +import React, { useState, useRef, useEffect, act } from "react"; import img from "../../assets/image/image.png"; import { useNavigate } from "react-router-dom"; import { getUserData } from "../../functions/getUserData"; @@ -14,13 +14,15 @@ interface DashBoardCardProps { projectId: string; createdAt?: string; isViewed?: string; + createdBy?: { _id: string, userName: string }; handleDeleteProject?: (projectId: string) => Promise; handleTrashDeleteProject?: (projectId: string) => Promise; handleRestoreProject?: (projectId: string) => Promise; handleDuplicateWorkspaceProject?: ( projectId: string, projectName: string, - thumbnail: string + thumbnail: string, + userId?: string ) => Promise; handleDuplicateRecentProject?: ( projectId: string, @@ -31,6 +33,7 @@ interface DashBoardCardProps { setIsSearchActive?: React.Dispatch>; setRecentDuplicateData?: React.Dispatch>; setProjectDuplicateData?: React.Dispatch>; + setActiveFolder?: React.Dispatch>; } type RelativeTimeFormatUnit = any; @@ -45,8 +48,10 @@ const DashboardCard: React.FC = ({ handleDuplicateWorkspaceProject, handleDuplicateRecentProject, createdAt, + createdBy, setRecentDuplicateData, setProjectDuplicateData, + setActiveFolder }) => { const navigate = useNavigate(); const { setProjectName } = useProjectName(); @@ -59,10 +64,18 @@ const DashboardCard: React.FC = ({ const kebabRef = useRef(null); const navigateToProject = async (e: any) => { + console.log('active: ', active); if (active && active == "trash") return; - setLoadingProgress(1) - setProjectName(projectName); - navigate(`/${projectId}`); + try { + const viewProjects = await viewProject(organization, projectId, userId) + console.log('viewProjects: ', viewProjects); + console.log('projectName: ', projectName); + setLoadingProgress(1) + setProjectName(projectName); + navigate(`/${projectId}`); + } catch { + + } }; const handleOptionClick = async (option: string) => { @@ -81,11 +94,18 @@ const DashboardCard: React.FC = ({ break; case "open in new tab": try { - await viewProject(organization, projectId, userId); - setProjectName(projectName); - setIsKebabOpen(false); + if (active === "shared" && createdBy) { + console.log("ihreq"); + const newTab = await viewProject(organization, projectId, createdBy?._id); + console.log('newTab: ', newTab); + } else { + const newTab = await viewProject(organization, projectId, userId); + console.log('newTab: ', newTab); + setProjectName(projectName); + setIsKebabOpen(false); + } } catch (error) { - console.error("Error opening project in new tab:", error); + } window.open(`/${projectId}`, "_blank"); break; @@ -100,13 +120,17 @@ const DashboardCard: React.FC = ({ projectName, thumbnail, }); - await handleDuplicateWorkspaceProject(projectId, projectName, thumbnail); + await handleDuplicateWorkspaceProject(projectId, projectName, thumbnail, userId); + if (active === "shared" && setActiveFolder) { + setActiveFolder("myProjects") + } } else if (handleDuplicateRecentProject) { setRecentDuplicateData && setRecentDuplicateData({ projectId, projectName, thumbnail, + userId }); await handleDuplicateRecentProject(projectId, projectName, thumbnail); } @@ -128,7 +152,6 @@ const DashboardCard: React.FC = ({ try { const projects = await getAllProjects(userId, organization); if (!projects || !projects.Projects) return; - // console.log("projects: ", projects); let projectUuid = projects.Projects.find( (val: any) => val.projectUuid === projectId || val._id === projectId ); @@ -173,6 +196,19 @@ const DashboardCard: React.FC = ({ return "just now"; } + const kebabOptionsMap: Record = { + default: ["rename", "delete", "duplicate", "open in new tab"], + trash: ["restore", "delete"], + shared: ["duplicate", "open in new tab"], + }; + + const getOptions = () => { + if (active === "trash") return kebabOptionsMap.trash; + if (active === "shared") return kebabOptionsMap.shared; + if (createdBy && createdBy?._id !== userId) return kebabOptionsMap.shared; + return kebabOptionsMap.default; + }; + return (
- {userName ? userName.charAt(0).toUpperCase() : "A"} + {(!createdBy) ? userName ? userName?.charAt(0).toUpperCase() : "A" : createdBy?.userName?.charAt(0).toUpperCase()}
- - {isKebabOpen && active !== "trash" && ( + {isKebabOpen && (
- {["rename", "delete", "duplicate", "open in new tab"].map( - (option) => ( - - ) - )} -
- )} - {isKebabOpen && active && active == "trash" && ( -
- {["restore", "delete"].map((option) => ( + {getOptions().map((option) => (