updated
This commit is contained in:
@@ -7,130 +7,102 @@ import { useProjectName, useSocketStore } from "../../store/builder/store";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getAllProjects } from "../../services/dashboard/getAllProjects";
|
||||
import { updateProject } from "../../services/dashboard/updateProject";
|
||||
import { getUserData } from "../../functions/getUserData";
|
||||
|
||||
const FileMenu: React.FC = () => {
|
||||
const [openMenu, setOpenMenu] = useState(false);
|
||||
const containerRef = useRef<HTMLButtonElement>(null);
|
||||
let clickTimeout: NodeJS.Timeout | null = null;
|
||||
const { projectName, setProjectName } = useProjectName();
|
||||
const { dashBoardSocket } = useSocketStore();
|
||||
const { projectId } = useParams();
|
||||
const [openMenu, setOpenMenu] = useState(false);
|
||||
const containerRef = useRef<HTMLButtonElement>(null);
|
||||
let clickTimeout: NodeJS.Timeout | null = null;
|
||||
const { projectName, setProjectName } = useProjectName();
|
||||
const { dashBoardSocket } = useSocketStore();
|
||||
const { projectId } = useParams();
|
||||
const { userId, organization, email } = getUserData();
|
||||
|
||||
const handleClick = () => {
|
||||
if (clickTimeout) return;
|
||||
setOpenMenu((prev) => !prev);
|
||||
clickTimeout = setTimeout(() => {
|
||||
clickTimeout = null;
|
||||
}, 800);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
containerRef.current &&
|
||||
!containerRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setOpenMenu(false);
|
||||
}
|
||||
const handleClick = () => {
|
||||
if (clickTimeout) return;
|
||||
setOpenMenu((prev) => !prev);
|
||||
clickTimeout = setTimeout(() => {
|
||||
clickTimeout = null;
|
||||
}, 800);
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
containerRef.current &&
|
||||
!containerRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setOpenMenu(false);
|
||||
}
|
||||
};
|
||||
|
||||
// project
|
||||
// const [projectName, setProjectName] = useState("Demo Project");
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
// Load project name from localStorage on mount
|
||||
// useEffect(() => {
|
||||
// const savedName = localStorage.getItem("projectName");
|
||||
// if (savedName) {
|
||||
// setProjectName(savedName);
|
||||
// }
|
||||
// }, []);
|
||||
const handleProjectRename = async (projectName: string) => {
|
||||
setProjectName(projectName);
|
||||
if (!projectId) return
|
||||
|
||||
// const handleProjectRename = (newName: string) => {
|
||||
// setProjectName(newName);
|
||||
// localStorage.setItem("projectName", newName);
|
||||
// };
|
||||
const handleProjectRename = async (projectName: string) => {
|
||||
setProjectName(projectName);
|
||||
if (!projectId) return
|
||||
// localStorage.setItem("projectName", newName);
|
||||
try {
|
||||
const email = localStorage.getItem("email");
|
||||
const userId = localStorage.getItem("userId");
|
||||
// localStorage.setItem("projectName", newName);
|
||||
|
||||
if (!email || !userId) {
|
||||
try {
|
||||
|
||||
return;
|
||||
}
|
||||
if (!email || !userId) return;
|
||||
|
||||
const emailParts = email.split("@");
|
||||
if (emailParts.length < 2) {
|
||||
const projects = await getAllProjects(userId, organization);
|
||||
// console.log('projects: ', projects);
|
||||
let projectUuid = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id === projectId)
|
||||
|
||||
return;
|
||||
}
|
||||
const updateProjects = {
|
||||
projectId: projectUuid,
|
||||
organization,
|
||||
userId,
|
||||
projectName,
|
||||
thumbnail: undefined
|
||||
}
|
||||
|
||||
const domainParts = emailParts[1].split(".");
|
||||
const Organization = domainParts[0];
|
||||
const projects = await getAllProjects(
|
||||
userId, Organization
|
||||
);
|
||||
console.log('projects: ', projects);
|
||||
let projectUuid = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id
|
||||
=== projectId)
|
||||
const updateProjects = {
|
||||
projectId: projectUuid,
|
||||
organization: Organization,
|
||||
userId,
|
||||
projectName,
|
||||
thumbnail: undefined
|
||||
}
|
||||
// if (dashBoardSocket) {
|
||||
// const handleResponse = (data: any) => {
|
||||
// console.log('Project update response:', data);
|
||||
// dashBoardSocket.off("v1-project:response:update", handleResponse); // Clean up
|
||||
// };
|
||||
// dashBoardSocket.on("v1-project:response:update", handleResponse);
|
||||
// dashBoardSocket.emit("v1:project:update", updateProjects);
|
||||
// }
|
||||
|
||||
// if (dashBoardSocket) {
|
||||
// const handleResponse = (data: any) => {
|
||||
// console.log('Project update response:', data);
|
||||
// dashBoardSocket.off("v1-project:response:update", handleResponse); // Clean up
|
||||
// };
|
||||
|
||||
// dashBoardSocket.on("v1-project:response:update", handleResponse);
|
||||
|
||||
// dashBoardSocket.emit("v1:project:update", updateProjects);
|
||||
// }
|
||||
|
||||
//API for projects rename
|
||||
const updatedProjectName = await updateProject(
|
||||
projectId,
|
||||
userId,
|
||||
Organization,
|
||||
undefined,
|
||||
projectName
|
||||
);
|
||||
//
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
};
|
||||
return (
|
||||
<button
|
||||
id="project-dropdown-button"
|
||||
className="project-dropdowm-container"
|
||||
ref={containerRef}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div className="project-name">
|
||||
<div className="icon">
|
||||
<ProjectIcon />
|
||||
</div>
|
||||
<RenameInput value={projectName} onRename={handleProjectRename} />
|
||||
</div>
|
||||
<div className="more-options-button">
|
||||
<ArrowIcon />
|
||||
{openMenu && <MenuBar setOpenMenu={setOpenMenu} />}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
//API for projects rename
|
||||
const updatedProjectName = await updateProject(
|
||||
projectId,
|
||||
userId,
|
||||
organization,
|
||||
undefined,
|
||||
projectName
|
||||
);
|
||||
//
|
||||
} catch (error) {
|
||||
console.error("Error updating project name:", error);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<button
|
||||
id="project-dropdown-button"
|
||||
className="project-dropdowm-container"
|
||||
ref={containerRef}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div className="project-name">
|
||||
<div className="icon">
|
||||
<ProjectIcon />
|
||||
</div>
|
||||
<RenameInput value={projectName} onRename={handleProjectRename} />
|
||||
</div>
|
||||
<div className="more-options-button">
|
||||
<ArrowIcon />
|
||||
{openMenu && <MenuBar setOpenMenu={setOpenMenu} />}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileMenu;
|
||||
|
||||
@@ -12,7 +12,7 @@ import useVersionHistoryStore from "../../store/builder/store";
|
||||
const ModuleToggle: React.FC = () => {
|
||||
const { activeModule, setActiveModule } = useModuleStore();
|
||||
const { setToggleUI } = useToggleStore();
|
||||
const { setVersionHistory } = useVersionHistoryStore();
|
||||
const { setVersionHistoryVisible } = useVersionHistoryStore();
|
||||
|
||||
return (
|
||||
<div className="module-toggle-container">
|
||||
@@ -21,7 +21,7 @@ const ModuleToggle: React.FC = () => {
|
||||
className={`module-list ${activeModule === "builder" ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setActiveModule("builder");
|
||||
setVersionHistory(false);
|
||||
setVersionHistoryVisible(false);
|
||||
setToggleUI(
|
||||
localStorage.getItem("navBarUiLeft")
|
||||
? localStorage.getItem("navBarUiLeft") === "true"
|
||||
@@ -44,7 +44,7 @@ const ModuleToggle: React.FC = () => {
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveModule("simulation");
|
||||
setVersionHistory(false);
|
||||
setVersionHistoryVisible(false);
|
||||
setToggleUI(
|
||||
localStorage.getItem("navBarUiLeft")
|
||||
? localStorage.getItem("navBarUiLeft") === "true"
|
||||
@@ -67,7 +67,7 @@ const ModuleToggle: React.FC = () => {
|
||||
}`}
|
||||
onClick={() => {
|
||||
setActiveModule("visualization");
|
||||
setVersionHistory(false);
|
||||
setVersionHistoryVisible(false);
|
||||
setToggleUI(
|
||||
localStorage.getItem("navBarUiLeft")
|
||||
? localStorage.getItem("navBarUiLeft") === "true"
|
||||
@@ -88,7 +88,7 @@ const ModuleToggle: React.FC = () => {
|
||||
className={`module-list ${activeModule === "market" ? "active" : ""}`}
|
||||
onClick={() => {
|
||||
setActiveModule("market");
|
||||
setVersionHistory(false);
|
||||
setVersionHistoryVisible(false);
|
||||
setToggleUI(false, false);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
useFloatingWidget,
|
||||
} from "../../store/visualization/useDroppedObjectsStore";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useVersionContext } from "../../modules/builder/version/versionContext";
|
||||
|
||||
// Utility component
|
||||
const ToolButton = ({
|
||||
@@ -86,6 +87,8 @@ const Tools: React.FC = () => {
|
||||
|
||||
const dropdownRef = useRef<HTMLButtonElement>(null);
|
||||
const [openDrop, setOpenDrop] = useState(false);
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion } = selectedVersionStore();
|
||||
const { projectId } = useParams();
|
||||
|
||||
// 1. Set UI toggles on initial render
|
||||
@@ -250,7 +253,8 @@ const Tools: React.FC = () => {
|
||||
selectedZone,
|
||||
templates,
|
||||
visualizationSocket,
|
||||
projectId
|
||||
projectId,
|
||||
versionId: selectedVersion?.versionId || ''
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
|
||||
import { getUserData } from "../../../functions/getUserData";
|
||||
import { getAllThreads } from "../../../services/factoryBuilder/comments/getAllThreads";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useCommentStore } from "../../../store/collaboration/useCommentStore";
|
||||
import { getRelativeTime } from "./function/getRelativeTime";
|
||||
import { useSelectedComment } from "../../../store/builder/store";
|
||||
|
||||
interface CommentThreadsProps {
|
||||
commentClicked: () => void;
|
||||
comment?: CommentSchema
|
||||
}
|
||||
|
||||
const CommentThreads: React.FC<CommentThreadsProps> = ({ commentClicked }) => {
|
||||
const CommentThreads: React.FC<CommentThreadsProps> = ({ commentClicked, comment }) => {
|
||||
const [expand, setExpand] = useState(false);
|
||||
const commentsedUsers = [{ creatorId: "1" }];
|
||||
const { userName } = getUserData();
|
||||
|
||||
const CommentDetails = {
|
||||
state: "active",
|
||||
@@ -16,26 +24,26 @@ const CommentThreads: React.FC<CommentThreadsProps> = ({ commentClicked }) => {
|
||||
createdAt: "2 hours ago",
|
||||
comment: "Thread check",
|
||||
lastUpdatedAt: "string",
|
||||
replies: [
|
||||
comments: [
|
||||
{
|
||||
replyId: "string",
|
||||
creatorId: "string",
|
||||
createdAt: "string",
|
||||
lastUpdatedAt: "string",
|
||||
reply: "string",
|
||||
comment: "string",
|
||||
},
|
||||
{
|
||||
replyId: "string",
|
||||
creatorId: "string",
|
||||
createdAt: "string",
|
||||
lastUpdatedAt: "string",
|
||||
reply: "string",
|
||||
comment: "string",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function getUsername(userId: string) {
|
||||
const UserName = "username";
|
||||
const UserName = userName?.charAt(0).toUpperCase() || "user";
|
||||
return UserName;
|
||||
}
|
||||
|
||||
@@ -48,15 +56,15 @@ const CommentThreads: React.FC<CommentThreadsProps> = ({ commentClicked }) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="comments-threads-wrapper">
|
||||
<button
|
||||
onPointerEnter={() => getDetails()}
|
||||
onPointerLeave={() => getDetails()}
|
||||
onClick={() => getDetails("clicked")}
|
||||
className={`comments-threads-container ${
|
||||
expand ? "open" : "closed"
|
||||
} unread`}
|
||||
className={`comments-threads-container ${expand ? "open" : "closed"
|
||||
} unread`}
|
||||
>
|
||||
<div className="users-commented">
|
||||
{commentsedUsers.map((val, i) => (
|
||||
@@ -70,24 +78,37 @@ const CommentThreads: React.FC<CommentThreadsProps> = ({ commentClicked }) => {
|
||||
{getUsername(val.creatorId)[0]}
|
||||
</div>
|
||||
))}
|
||||
{/* {commentsedUsers.map((val, i) => (
|
||||
<div
|
||||
className="users"
|
||||
key={val.creatorId}
|
||||
style={{
|
||||
background: getAvatarColor(i, getUsername(val.creatorId)),
|
||||
}}
|
||||
>
|
||||
{getUsername(val.creatorId)[0]}
|
||||
</div>
|
||||
))} */}
|
||||
</div>
|
||||
<div className={`last-comment-details ${expand ? "expand" : ""}`}>
|
||||
<div className="header">
|
||||
<div className="user-name">
|
||||
{getUsername(CommentDetails.creatorId)}
|
||||
{userName}
|
||||
{/* {getUsername(CommentDetails.creatorId)} */}
|
||||
</div>
|
||||
<div className="time">{CommentDetails.createdAt}</div>
|
||||
<div className="time">{comment?.createdAt && getRelativeTime(comment.createdAt)}</div>
|
||||
</div>
|
||||
<div className="message">{CommentDetails.comment}</div>
|
||||
{CommentDetails.replies.length > 0 && (
|
||||
<div className="replies">
|
||||
{CommentDetails.replies.length}{" "}
|
||||
{CommentDetails.replies.length === 1 ? "reply" : "replies"}
|
||||
<div className="message">{comment?.threadTitle}</div>
|
||||
{comment && comment?.comments.length > 0 && (
|
||||
<div className="comments">
|
||||
{comment && comment?.comments.length}{" "}
|
||||
{comment && comment?.comments.length === 1 ? "comment" : "replies"}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,44 +2,181 @@ import React, { useEffect, useRef, useState } from "react";
|
||||
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
|
||||
import { KebabIcon } from "../../icons/ExportCommonIcons";
|
||||
import { adjustHeight } from "./function/textAreaHeightAdjust";
|
||||
import { getUserData } from "../../../functions/getUserData";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { deleteCommentApi } from "../../../services/factoryBuilder/comments/deleteCommentApi";
|
||||
import { addCommentsApi } from "../../../services/factoryBuilder/comments/addCommentsApi";
|
||||
import { useCommentStore } from "../../../store/collaboration/useCommentStore";
|
||||
import { useSelectedComment, useSocketStore } from "../../../store/builder/store";
|
||||
import { getRelativeTime } from "./function/getRelativeTime";
|
||||
import { editThreadTitleApi } from "../../../services/factoryBuilder/comments/editThreadTitleApi";
|
||||
|
||||
|
||||
interface MessageProps {
|
||||
val: Reply | CommentSchema;
|
||||
// val: Reply | CommentSchema;
|
||||
i: number;
|
||||
setMessages?: React.Dispatch<React.SetStateAction<Reply[]>>
|
||||
setIsEditable?: React.Dispatch<React.SetStateAction<boolean>>
|
||||
setEditedThread?: React.Dispatch<React.SetStateAction<boolean>>
|
||||
setMode?: React.Dispatch<React.SetStateAction<'create' | 'edit' | null>>
|
||||
isEditable?: boolean;
|
||||
isEditableThread?: boolean
|
||||
editedThread?: boolean;
|
||||
mode?: 'create' | 'edit' | null
|
||||
}
|
||||
|
||||
const Messages: React.FC<MessageProps> = ({ val, i }) => {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEditable, setEditedThread, editedThread, isEditableThread, setMode }) => {
|
||||
|
||||
const { comments, updateComment, updateReply, removeReply } = useCommentStore();
|
||||
const [openOptions, setOpenOptions] = useState(false);
|
||||
const { projectId } = useParams();
|
||||
const { threadSocket } = useSocketStore();
|
||||
const { userName, userId, organization } = getUserData();
|
||||
const [isEditComment, setIsEditComment] = useState(false)
|
||||
|
||||
const { selectedComment, setCommentPositionState } = useSelectedComment();
|
||||
|
||||
// input
|
||||
const [value, setValue] = useState<string>(
|
||||
"reply" in val ? val.reply : val.comment
|
||||
"comment" in val ? val.comment : val.threadTitle
|
||||
);
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const currentUser = "1";
|
||||
|
||||
const UserName = "username";
|
||||
// const UserName = "username";
|
||||
|
||||
useEffect(() => {
|
||||
if (textareaRef.current) adjustHeight(textareaRef.current);
|
||||
}, [value]);
|
||||
|
||||
function handleCancelAction() {
|
||||
setIsEditing(false);
|
||||
setCommentPositionState(null)
|
||||
setIsEditable && setIsEditable(true);
|
||||
setIsEditComment(false)
|
||||
}
|
||||
|
||||
function handleSaveAction() {
|
||||
setIsEditing(false);
|
||||
const handleSaveAction = async () => {
|
||||
|
||||
if (!projectId) return
|
||||
|
||||
if (isEditableThread && editedThread) {
|
||||
try {
|
||||
// const editThreadTitle = await editThreadTitleApi(projectId, (val as CommentSchema).threadId, value)
|
||||
// if (editThreadTitle.message == "ThreadTitle updated Successfully") {
|
||||
// const editedThread: CommentSchema = {
|
||||
// state: 'active',
|
||||
// threadId: editThreadTitle.data.replyId,
|
||||
// creatorId: userId,
|
||||
// createdAt: getRelativeTime(editThreadTitle.data.createdAt),
|
||||
// threadTitle: value,
|
||||
// lastUpdatedAt: new Date().toISOString(),
|
||||
// position: editThreadTitle.data.position,
|
||||
// rotation: [0, 0, 0],
|
||||
// comments: []
|
||||
// }
|
||||
// updateComment((val as CommentSchema).threadId, editedThread)
|
||||
// }
|
||||
// projectId, userId, threadTitle, organization, threadId
|
||||
const threadEdit = {
|
||||
projectId,
|
||||
userId,
|
||||
threadTitle: value,
|
||||
organization,
|
||||
threadId: (val as CommentSchema).threadId
|
||||
}
|
||||
|
||||
threadSocket.emit('v1:thread:updateTitle', threadEdit)
|
||||
} catch {
|
||||
}
|
||||
} else {
|
||||
|
||||
if (mode === "edit") {
|
||||
try {
|
||||
// const editComments = await addCommentsApi(projectId, value, selectedComment?.threadId, (val as Reply).replyId)
|
||||
//
|
||||
// const commentData = {
|
||||
// replyId: `${editComments.data?.replyId}`,
|
||||
// creatorId: `${userId}`,
|
||||
// createdAt: getRelativeTime(editComments.data?.createdAt),
|
||||
// lastUpdatedAt: "2 hrs ago",
|
||||
// comment: value,
|
||||
// }
|
||||
|
||||
// updateReply((val as CommentSchema).threadId, (val as Reply)?.replyId, commentData);
|
||||
|
||||
if (threadSocket) {
|
||||
// projectId, userId, comment, organization, threadId
|
||||
const editComment = {
|
||||
projectId,
|
||||
userId,
|
||||
comment: value,
|
||||
organization,
|
||||
threadId: selectedComment?.threadId,
|
||||
commentId: (val as Reply).replyId ?? ""
|
||||
}
|
||||
|
||||
|
||||
threadSocket.emit("v1-Comment:add", editComment);
|
||||
setIsEditable && setIsEditable(true);
|
||||
setEditedThread && setEditedThread(false)
|
||||
}
|
||||
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// setValue("");
|
||||
setIsEditComment(false);
|
||||
}
|
||||
|
||||
function handleDeleteAction() {
|
||||
const handleDeleteAction = async (replyID: any) => {
|
||||
if (!projectId) return
|
||||
setOpenOptions(false);
|
||||
try {
|
||||
// const deletedComment = await deleteCommentApi(projectId, selectedComment?.threadId, (val as Reply).replyId)
|
||||
//
|
||||
// if (deletedComment === "'Thread comment deleted Successfully'") {
|
||||
// setMessages && setMessages(prev => prev.filter(message => message.replyId !== replyID));
|
||||
// removeReply(val.creatorId, replyID)
|
||||
// }
|
||||
if (threadSocket && setMessages) {
|
||||
|
||||
|
||||
// projectId, userId, commentId, organization, threadId
|
||||
const deleteComment = {
|
||||
projectId,
|
||||
userId,
|
||||
commentId: (val as Reply).replyId,
|
||||
organization,
|
||||
threadId: selectedComment?.threadId
|
||||
}
|
||||
|
||||
|
||||
setMessages(prev => prev.filter(message => message.replyId !== (val as Reply).replyId))
|
||||
|
||||
removeReply(selectedComment?.threadId, (val as Reply).replyId); // Remove listener after response
|
||||
threadSocket.emit("v1-Comment:delete", deleteComment);
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const handleFocus = (e: React.FocusEvent<HTMLTextAreaElement>) => {
|
||||
requestAnimationFrame(() => {
|
||||
if (textareaRef.current) {
|
||||
const length = textareaRef.current.value.length;
|
||||
textareaRef.current.setSelectionRange(length, length);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isEditing ? (
|
||||
{isEditComment ? (
|
||||
<div className="edit-container">
|
||||
<div className="input-container">
|
||||
<textarea
|
||||
@@ -49,6 +186,7 @@ const Messages: React.FC<MessageProps> = ({ val, i }) => {
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
style={{ resize: "none" }}
|
||||
onFocus={handleFocus}
|
||||
/>
|
||||
</div>
|
||||
<div className="actions-container">
|
||||
@@ -77,16 +215,16 @@ const Messages: React.FC<MessageProps> = ({ val, i }) => {
|
||||
<div className="message-container">
|
||||
<div
|
||||
className="profile"
|
||||
style={{ background: getAvatarColor(i, UserName) }}
|
||||
style={{ background: getAvatarColor(i, userName) }}
|
||||
>
|
||||
{UserName[0]}
|
||||
{userName?.charAt(0).toUpperCase() || "user"}
|
||||
</div>
|
||||
<div className="content">
|
||||
<div className="user-details">
|
||||
<div className="user-name">{UserName}</div>
|
||||
<div className="time">{val.createdAt}</div>
|
||||
<div className="user-name">{userName}</div>
|
||||
<div className="time">{isEditableThread ? getRelativeTime(val.createdAt) : val.createdAt}</div>
|
||||
</div>
|
||||
{val.creatorId === currentUser && (
|
||||
{(val as Reply).creatorId === userId && (
|
||||
<div className="more-options">
|
||||
<button
|
||||
className="more-options-button"
|
||||
@@ -100,30 +238,33 @@ const Messages: React.FC<MessageProps> = ({ val, i }) => {
|
||||
<div className="options-list">
|
||||
<button
|
||||
className="option"
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setMode && setMode("edit")
|
||||
setOpenOptions(false);
|
||||
setIsEditing(true);
|
||||
setEditedThread && setEditedThread(true);
|
||||
setIsEditComment(true)
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button
|
||||
{!(isEditableThread) && <button
|
||||
className="option"
|
||||
onClick={() => {
|
||||
handleDeleteAction();
|
||||
handleDeleteAction((val as Reply).replyId);
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</button>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="message">
|
||||
{"reply" in val ? val.reply : val.comment}
|
||||
{"comment" in val ? val.comment : val.threadTitle}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div >
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -3,36 +3,67 @@ import { CloseIcon, KebabIcon } from "../../icons/ExportCommonIcons";
|
||||
import Messages from "./Messages";
|
||||
import { ExpandIcon } from "../../icons/SimulationIcons";
|
||||
import { adjustHeight } from "./function/textAreaHeightAdjust";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useSelectedComment, useSocketStore } from "../../../store/builder/store";
|
||||
import { useCommentStore } from "../../../store/collaboration/useCommentStore";
|
||||
import { getUserData } from "../../../functions/getUserData";
|
||||
import ThreadSocketResponsesDev from "../../../modules/collaboration/socket/threadSocketResponses.dev";
|
||||
import { addCommentsApi } from "../../../services/factoryBuilder/comments/addCommentsApi";
|
||||
import { deleteThreadApi } from "../../../services/factoryBuilder/comments/deleteThreadApi";
|
||||
import { createThreadApi } from "../../../services/factoryBuilder/comments/createThreadApi";
|
||||
import { getRelativeTime } from "./function/getRelativeTime";
|
||||
|
||||
|
||||
const ThreadChat: React.FC = () => {
|
||||
const { userId, organization } = getUserData();
|
||||
const [openThreadOptions, setOpenThreadOptions] = useState(false);
|
||||
const [inputActive, setInputActive] = useState(false);
|
||||
const [value, setValue] = useState<string>("");
|
||||
|
||||
const { addComment, removeReply, removeComment, addReply, comments } = useCommentStore();
|
||||
const { selectedComment, setSelectedComment, setCommentPositionState, commentPositionState, position2Dstate } = useSelectedComment()
|
||||
const [mode, setMode] = useState<'create' | 'edit' | null>('create');
|
||||
const [isEditable, setIsEditable] = useState(false);
|
||||
const [editedThread, setEditedThread] = useState(false);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [messages, setMessages] = useState<Reply[]>([])
|
||||
const { projectId } = useParams();
|
||||
const [dragging, setDragging] = useState(false);
|
||||
const [selectedDiv, setSelectedDiv] = useState(true);
|
||||
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
|
||||
const [position, setPosition] = useState({ x: 100, y: 100 });
|
||||
const [position, setPosition] = useState({ x: position2Dstate.x, y: position2Dstate.y });
|
||||
const { threadSocket } = useSocketStore();
|
||||
const modeRef = useRef<'create' | 'edit' | null>(null);
|
||||
const messagesRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const messages = [
|
||||
{
|
||||
replyId: "user 1",
|
||||
creatorId: "1",
|
||||
createdAt: "2 hrs ago",
|
||||
lastUpdatedAt: "2 hrs ago",
|
||||
reply:
|
||||
"reply testing reply content 1, reply testing reply content 1reply testing reply content 1",
|
||||
},
|
||||
{
|
||||
replyId: "user 2",
|
||||
creatorId: "2",
|
||||
createdAt: "2 hrs ago",
|
||||
lastUpdatedAt: "2 hrs ago",
|
||||
reply: "reply 2",
|
||||
},
|
||||
];
|
||||
useEffect(() => {
|
||||
modeRef.current = mode;
|
||||
}, [mode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (comments.length > 0 && selectedComment) {
|
||||
|
||||
|
||||
const allMessages = comments
|
||||
.flatMap((val: any) =>
|
||||
val?.threadId === selectedComment?.threadId ? val.comments : []
|
||||
)
|
||||
.map((c) => {
|
||||
return {
|
||||
replyId: c._id ?? "",
|
||||
creatorId: c.creatorId || c.userId,
|
||||
createdAt: c.createdAt,
|
||||
lastUpdatedAt: "1 hr ago",
|
||||
comment: c.comment,
|
||||
_id: c._id ?? "",
|
||||
};
|
||||
});
|
||||
|
||||
setMessages(allMessages);
|
||||
|
||||
}
|
||||
|
||||
}, [selectedComment])
|
||||
|
||||
useEffect(() => {
|
||||
if (textareaRef.current) adjustHeight(textareaRef.current);
|
||||
@@ -44,6 +75,19 @@ const ThreadChat: React.FC = () => {
|
||||
|
||||
const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (event.button !== 0) return;
|
||||
// Avoid dragging if a button, icon, textarea etc. was clicked
|
||||
const target = event.target as HTMLElement;
|
||||
if (
|
||||
target.closest("button") ||
|
||||
target.closest(".sent-button") ||
|
||||
target.closest("textarea") ||
|
||||
target.closest(".options-button") ||
|
||||
target.closest(".options-list") ||
|
||||
target.closest(".send-message-wrapper") ||
|
||||
target.closest(".options delete")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wrapper = wrapperRef.current;
|
||||
if (!wrapper) return;
|
||||
@@ -58,18 +102,20 @@ const ThreadChat: React.FC = () => {
|
||||
wrapper.setPointerCapture(event.pointerId);
|
||||
};
|
||||
|
||||
const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (!dragging) return;
|
||||
const updatePosition = (
|
||||
{ clientX, clientY }: { clientX: number; clientY: number },
|
||||
allowMove: boolean = true
|
||||
) => {
|
||||
if (!allowMove || !wrapperRef.current) return;
|
||||
|
||||
const container = document.getElementById("work-space-three-d-canvas");
|
||||
const wrapper = wrapperRef.current;
|
||||
if (!container || !wrapper) return;
|
||||
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const wrapperRect = wrapper.getBoundingClientRect();
|
||||
|
||||
let newX = event.clientX - containerRect.left - dragOffset.x;
|
||||
let newY = event.clientY - containerRect.top - dragOffset.y;
|
||||
let newX = clientX - containerRect.left - dragOffset.x;
|
||||
let newY = clientY - containerRect.top - dragOffset.y;
|
||||
|
||||
const maxX = containerRect.width - wrapper.offsetWidth;
|
||||
const maxY = containerRect.height - wrapper.offsetHeight;
|
||||
@@ -80,6 +126,15 @@ const ThreadChat: React.FC = () => {
|
||||
setPosition({ x: newX, y: newY });
|
||||
};
|
||||
|
||||
const handlePointerMove = (e: { clientX: number; clientY: number }) => {
|
||||
if (dragging) updatePosition(e, true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updatePosition({ clientX: position.x, clientY: position.y }, true);
|
||||
}, [selectedComment]);
|
||||
|
||||
|
||||
const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (!dragging) return;
|
||||
setDragging(false);
|
||||
@@ -87,12 +142,146 @@ const ThreadChat: React.FC = () => {
|
||||
if (wrapper) wrapper.releasePointerCapture(event.pointerId);
|
||||
};
|
||||
|
||||
const handleCreateComments = async (e: any) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
// const createComments = await addCommentsApi(projectId, value, selectedComment?.threadId)/
|
||||
// if (createComments.message === 'Thread comments add Successfully' && createComments.data) {
|
||||
// const commentData = {
|
||||
// replyId: `${createComments.data?._id}`,
|
||||
// creatorId: `${selectedComment?.threadId}`,
|
||||
// createdAt: "2 hrs ago",
|
||||
// lastUpdatedAt: "2 hrs ago",
|
||||
// comment: value,
|
||||
// }
|
||||
// setMessages((prevMessages) => [
|
||||
// ...prevMessages,
|
||||
// commentData,
|
||||
// ]);
|
||||
// addReply(selectedComment?.threadId, commentData)
|
||||
|
||||
// }
|
||||
|
||||
if (threadSocket && mode === "create") {
|
||||
const addComment = {
|
||||
projectId,
|
||||
userId,
|
||||
comment: value,
|
||||
organization,
|
||||
threadId: selectedComment?.threadId
|
||||
}
|
||||
|
||||
threadSocket.emit("v1-Comment:add", addComment);
|
||||
}
|
||||
} catch {
|
||||
|
||||
}
|
||||
setInputActive(false)
|
||||
}
|
||||
const handleDeleteThread = async () => {
|
||||
if (!projectId) return;
|
||||
try {
|
||||
// const deleteThread = await deleteThreadApi(projectId, selectedComment?.threadId)
|
||||
//
|
||||
// if (deleteThread.message === "Thread deleted Successfully") {
|
||||
// removeComment(selectedComment?.threadId)
|
||||
// setSelectedComment([])
|
||||
// }
|
||||
if (threadSocket) {
|
||||
// projectId, userId, organization, threadId
|
||||
const deleteThread = {
|
||||
projectId,
|
||||
userId,
|
||||
organization,
|
||||
threadId: selectedComment?.threadId
|
||||
}
|
||||
|
||||
setSelectedComment(null)
|
||||
removeComment(selectedComment?.threadId)
|
||||
threadSocket.emit("v1:thread:delete", deleteThread);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreateThread = async (e: any) => {
|
||||
e.preventDefault();
|
||||
if (!projectId) return;
|
||||
|
||||
try {
|
||||
// try {
|
||||
// const thread = await createThreadApi(
|
||||
// projectId,
|
||||
// "active",
|
||||
// commentPositionState[0].position,
|
||||
// [0, 0, 0],
|
||||
// value
|
||||
// );
|
||||
//
|
||||
//
|
||||
|
||||
// if (thread.message === "Thread created Successfully" && thread?.threadData) {
|
||||
//
|
||||
// const comment: CommentSchema = {
|
||||
// state: 'active',
|
||||
// threadId: thread?.threadData?._id,
|
||||
// creatorId: userId,
|
||||
// createdAt: getRelativeTime(thread.threadData?.createdAt),
|
||||
// threadTitle: value,
|
||||
// lastUpdatedAt: new Date().toISOString(),
|
||||
// position: commentPositionState[0].position,
|
||||
// rotation: [0, 0, 0],
|
||||
// comments: []
|
||||
// }
|
||||
// addComment(comment);
|
||||
// setCommentPositionState(null);
|
||||
// setInputActive(false);
|
||||
// setSelectedComment([])
|
||||
// }
|
||||
|
||||
const createThread = {
|
||||
projectId,
|
||||
userId,
|
||||
organization,
|
||||
state: "active",
|
||||
position: commentPositionState.position,
|
||||
rotation: [0, 0, 0],
|
||||
threadTitle: value
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
if (threadSocket) {
|
||||
|
||||
setInputActive(false);
|
||||
threadSocket.emit("v1:thread:create", createThread);
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const scrollToBottom = () => {
|
||||
const messagesWrapper = document.querySelector(".messages-wrapper");
|
||||
if (messagesWrapper) {
|
||||
messagesWrapper.scrollTop = messagesWrapper.scrollHeight;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (messages.length > 0)
|
||||
scrollToBottom();
|
||||
}, [messages])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className="thread-chat-wrapper"
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerMove={(e) => handlePointerMove({ clientX: e.clientX, clientY: e.clientY })}
|
||||
onPointerUp={handlePointerUp}
|
||||
style={{
|
||||
position: "absolute",
|
||||
@@ -107,49 +296,94 @@ const ThreadChat: React.FC = () => {
|
||||
<div className="header-wrapper">
|
||||
<div className="header">Comment</div>
|
||||
<div className="header-options">
|
||||
<button
|
||||
<div
|
||||
className="options-button"
|
||||
onClick={() => setOpenThreadOptions(!openThreadOptions)}
|
||||
style={{ cursor: "pointer" }}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setOpenThreadOptions((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
<KebabIcon />
|
||||
</button>
|
||||
</div>
|
||||
{openThreadOptions && (
|
||||
<div className="options-list">
|
||||
<div className="options">Mark as Unread</div>
|
||||
<div className="options">Mark as Resolved</div>
|
||||
<div className="options delete">Delete Thread</div>
|
||||
<div className="options delete" onClick={handleDeleteThread}>Delete Thread</div>
|
||||
</div>
|
||||
)}
|
||||
<button className="close-button">
|
||||
<button className="close-button" onClick={() => {
|
||||
setSelectedComment(null)
|
||||
setCommentPositionState(null)
|
||||
}}>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="messages-wrapper">
|
||||
{messages.map((val, i) => (
|
||||
<Messages val={val as any} i={i} key={val.replyId} />
|
||||
<div className="messages-wrapper" ref={messagesRef}>
|
||||
{selectedComment &&
|
||||
<Messages val={selectedComment} i={1} key={selectedComment.creatorId} isEditableThread={true} setEditedThread={setEditedThread} editedThread={editedThread} />
|
||||
}
|
||||
{messages && messages.map((val, i) => (
|
||||
<Messages val={val as any} i={i} key={val.replyId} setMessages={setMessages} setIsEditable={setIsEditable} isEditable={isEditable} isEditableThread={false} setMode={setMode} mode={mode} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="send-message-wrapper">
|
||||
<div className={`input-container ${inputActive ? "active" : ""}`}>
|
||||
<textarea
|
||||
placeholder="type something"
|
||||
placeholder={commentPositionState && selectedComment === null ? "Type Thread Title" : "type something"}
|
||||
ref={textareaRef}
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onFocus={() => setInputActive(true)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
if (commentPositionState && selectedComment === null) {
|
||||
handleCreateThread(e);
|
||||
} else {
|
||||
setMode("create");
|
||||
handleCreateComments(e);
|
||||
textareaRef.current?.blur();
|
||||
}
|
||||
setValue('')
|
||||
}
|
||||
if (e.key === "Escape") {
|
||||
textareaRef.current?.blur();
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
if (!commentPositionState && selectedComment !== null) {
|
||||
setMode("create");
|
||||
}
|
||||
}}
|
||||
autoFocus={selectedComment === null}
|
||||
onBlur={() => setInputActive(false)}
|
||||
onFocus={() => setInputActive(true)}
|
||||
style={{ resize: "none" }}
|
||||
/>
|
||||
<div className={`sent-button ${value === "" ? "disable-send-btn" : ""}`}>
|
||||
<div
|
||||
className={`sent-button ${value === "" ? "disable-send-btn" : ""}`}
|
||||
onClick={(e) => {
|
||||
if (commentPositionState && selectedComment === null) {
|
||||
handleCreateThread(e);
|
||||
} else {
|
||||
setMode("create");
|
||||
handleCreateComments(e);
|
||||
}
|
||||
setValue('')
|
||||
}}
|
||||
>
|
||||
<ExpandIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<ThreadSocketResponsesDev setMessages={setMessages} modeRef={modeRef} messages={messages} />
|
||||
</div >
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
type RelativeTimeFormatUnit = any;
|
||||
export function getRelativeTime(dateString: string): string {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
||||
|
||||
const intervals: Record<RelativeTimeFormatUnit, number> = {
|
||||
year: 31536000,
|
||||
month: 2592000,
|
||||
week: 604800,
|
||||
day: 86400,
|
||||
hour: 3600,
|
||||
minute: 60,
|
||||
second: 1,
|
||||
};
|
||||
|
||||
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
||||
|
||||
for (const key in intervals) {
|
||||
const unit = key as RelativeTimeFormatUnit;
|
||||
const diff = Math.floor(diffInSeconds / intervals[unit]);
|
||||
if (diff >= 1) {
|
||||
return rtf.format(-diff, unit);
|
||||
}
|
||||
}
|
||||
return "just now";
|
||||
}
|
||||
@@ -10,15 +10,22 @@ import {
|
||||
} from "../../../store/builder/store";
|
||||
import Search from "../inputs/Search";
|
||||
import OuterClick from "../../../utils/outerClick";
|
||||
import { useProductStore } from "../../../store/simulation/useProductStore";
|
||||
import Scene from "../../../modules/scene/scene";
|
||||
import { useComparisonProduct } from "../../../store/simulation/useSimulationStore";
|
||||
import { usePauseButtonStore, usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||
import { useVersionHistoryStore } from "../../../store/builder/useVersionHistoryStore";
|
||||
import { useVersionContext } from "../../../modules/builder/version/versionContext";
|
||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||
import { getAllProductsApi } from "../../../services/simulation/products/getallProductsApi";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
const CompareLayOut = () => {
|
||||
const { comparisonProduct, setComparisonProduct, clearComparisonProduct } =
|
||||
useComparisonProduct();
|
||||
const { products } = useProductStore();
|
||||
const { clearComparisonProduct, comparisonProduct, setComparisonProduct } = useComparisonProduct();
|
||||
const { productStore } = useSceneContext();
|
||||
const { products } = productStore();
|
||||
const { versionHistory } = useVersionHistoryStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion, setSelectedVersion, clearSelectedVersion } = selectedVersionStore();
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const [width, setWidth] = useState("50vw");
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
@@ -29,7 +36,13 @@ const CompareLayOut = () => {
|
||||
const { setIsVersionSaved } = useSaveVersion();
|
||||
const { loadingProgress } = useLoadingProgress();
|
||||
const { setIsPlaying } = usePlayButtonStore();
|
||||
const { setIsPaused } = usePauseButtonStore();
|
||||
const { projectId } = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
if (!comparisonProduct) {
|
||||
clearSelectedVersion();
|
||||
}
|
||||
}, [comparisonProduct])
|
||||
|
||||
OuterClick({
|
||||
contextClassName: ["displayLayouts-container", "selectLayout"],
|
||||
@@ -113,12 +126,14 @@ const CompareLayOut = () => {
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, [isResizing]);
|
||||
|
||||
const handleSelectLayout = (option: string) => {
|
||||
const product = products.find((product) => product.productName === option);
|
||||
if (product) {
|
||||
setComparisonProduct(product.productUuid, product.productName);
|
||||
setLoadingProgress(1);
|
||||
}
|
||||
const handleSelectLayout = (version: Version) => {
|
||||
getAllProductsApi(projectId || '', version.versionId || '').then((data) => {
|
||||
if (data && data.length > 0) {
|
||||
setSelectedVersion(version);
|
||||
setComparisonProduct(data[0].productUuid, data[0].productName);
|
||||
setLoadingProgress(1);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -127,7 +142,7 @@ const CompareLayOut = () => {
|
||||
ref={wrapperRef}
|
||||
style={{ width }}
|
||||
>
|
||||
{loadingProgress == 0 && (
|
||||
{loadingProgress == 0 && selectedVersion?.versionId && (
|
||||
<button
|
||||
title="resize-canvas"
|
||||
id="compare-resize-slider-btn"
|
||||
@@ -138,7 +153,7 @@ const CompareLayOut = () => {
|
||||
</button>
|
||||
)}
|
||||
<div className="chooseLayout-container">
|
||||
{comparisonProduct && (
|
||||
{selectedVersion?.versionId && (
|
||||
<div className="compare-layout-canvas-container">
|
||||
<Suspense fallback={null}>
|
||||
<Scene layout="Comparison Layout" />
|
||||
@@ -147,35 +162,35 @@ const CompareLayOut = () => {
|
||||
)}
|
||||
|
||||
{width !== "0px" &&
|
||||
!comparisonProduct && ( // Show only if no layout selected
|
||||
!selectedVersion?.versionId && ( // Show only if no layout selected
|
||||
<div className="chooseLayout-wrapper">
|
||||
<div className="icon">
|
||||
<CompareLayoutIcon />
|
||||
</div>
|
||||
<div className="value">Choose Layout to compare</div>
|
||||
<div className="value">Choose Version to compare</div>
|
||||
<button
|
||||
className="selectLayout"
|
||||
onClick={() => setShowLayoutDropdown(!showLayoutDropdown)}
|
||||
>
|
||||
Select Layout
|
||||
Select Version
|
||||
</button>
|
||||
|
||||
{showLayoutDropdown && (
|
||||
<div className="displayLayouts-container">
|
||||
<div className="header">Layouts</div>
|
||||
<Search onChange={() => {}} />
|
||||
<div className="header">Versions</div>
|
||||
<Search onChange={() => { }} />
|
||||
<div className="layouts-container">
|
||||
{products.map((layout) => (
|
||||
{versionHistory.map((version) => (
|
||||
<button
|
||||
key={layout.productUuid}
|
||||
key={version.versionId}
|
||||
className="layout-wrapper"
|
||||
onClick={() => {
|
||||
handleSelectLayout(layout.productName);
|
||||
handleSelectLayout(version);
|
||||
setShowLayoutDropdown(false);
|
||||
}}
|
||||
>
|
||||
<LayoutIcon />
|
||||
<div className="layout">{layout.productName}</div>
|
||||
<div className="layout">{version.versionName}</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -133,7 +133,7 @@ const ComparisonResult = () => {
|
||||
<div className="compare-result-container">
|
||||
<div className="header">Performance Comparison</div>
|
||||
<div className="compare-result-wrapper">
|
||||
<EnergyUsage />
|
||||
<EnergyUsage comparedProducts={comparedProducts}/>
|
||||
<div className="throughPutCard-container comparisionCard">
|
||||
<h4>Throughput (units/hr)</h4>
|
||||
<div className="layers-wrapper">
|
||||
@@ -153,7 +153,7 @@ const ComparisonResult = () => {
|
||||
|
||||
<div className="cycle-time-container comparisionCard">
|
||||
<div className="cycle-main">
|
||||
<div className="cycle-header">Cycle Time</div>
|
||||
<h4 className="cycle-header">Cycle Time</h4>
|
||||
<div className="layers-wrapper">
|
||||
<div className="layers">
|
||||
<div className="layer-name">{comparedProducts[0]?.productName}</div>
|
||||
@@ -202,7 +202,7 @@ const ComparisonResult = () => {
|
||||
</div>
|
||||
{/*
|
||||
<div className="overallDowntime-container comparisionCard">
|
||||
<div className="overallDowntime-header">Overall Downtime</div>
|
||||
<h4 className="overallDowntime-header">Overall Downtime</h4>
|
||||
<div className="totalDownTime-wrapper">
|
||||
<div className="totalDownTime">
|
||||
<div className="totalDownTime-right">
|
||||
@@ -221,7 +221,7 @@ const ComparisonResult = () => {
|
||||
</div> */}
|
||||
|
||||
<div className="overallScrapRate comparisionCard">
|
||||
<div className="overallScrapRate-header">Production Capacity</div>
|
||||
<h4 className="overallScrapRate-header">Production Capacity</h4>
|
||||
<div className="overallScrapRate-wrapper">
|
||||
<div className="overallScrapRate-value">
|
||||
<div className="overallScrapRate-label">{highestProductivityProduct?.productName}</div>
|
||||
|
||||
@@ -19,7 +19,7 @@ ChartJS.register(
|
||||
Legend
|
||||
);
|
||||
|
||||
const EnergyUsage = () => {
|
||||
const EnergyUsage = ({comparedProducts}:any) => {
|
||||
const data = useMemo(() => {
|
||||
const randomizeData = () =>
|
||||
Array.from({ length: 5 }, () => Math.floor(Math.random() * (2000 - 300 + 1)) + 300);
|
||||
@@ -90,14 +90,14 @@ const EnergyUsage = () => {
|
||||
<div className="simulation-wrapper sim-1">
|
||||
<div className="icon"></div>
|
||||
<div className="simulation-details-wrapper">
|
||||
<div className="label">Simulation 1</div>
|
||||
<div className="label">{comparedProducts[0]?.productName}</div>
|
||||
<div className="value">98%</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="simulation-wrapper sim-2">
|
||||
<div className="icon"></div>
|
||||
<div className="simulation-details-wrapper">
|
||||
<div className="label">Simulation 2</div>
|
||||
<div className="label">{comparedProducts[1]?.productName}</div>
|
||||
<div className="value">97%</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@ const PerformanceResult = ({ comparedProducts }: any) => {
|
||||
<div className="icon">
|
||||
<PerformanceIcon />
|
||||
</div>
|
||||
<div className="head">Performance result</div>
|
||||
<h4 className="head">Performance result</h4>
|
||||
</div>
|
||||
|
||||
<div className="metrics-container">
|
||||
@@ -56,7 +56,7 @@ const PerformanceResult = ({ comparedProducts }: any) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="simulation-tag">Simulation 1</div>
|
||||
<div className="simulation-tag">{ProfitProduct.productName}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import List from "./List";
|
||||
import { AddIcon, ArrowIcon, FocusIcon } from "../../icons/ExportCommonIcons";
|
||||
import KebabMenuListMultiSelect from "./KebebMenuListMultiSelect";
|
||||
import { useZones } from "../../../store/builder/store";
|
||||
import { useAssetsStore } from "../../../store/builder/useAssetStore";
|
||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||
|
||||
interface DropDownListProps {
|
||||
value?: string; // Value to display in the DropDownList
|
||||
@@ -51,8 +51,9 @@ const DropDownList: React.FC<DropDownListProps> = ({
|
||||
};
|
||||
|
||||
const [zoneDataList, setZoneDataList] = useState<ZoneData[]>([]);
|
||||
const { assets } = useAssetsStore();
|
||||
|
||||
const { assetStore } = useSceneContext();
|
||||
const { assets } = assetStore();
|
||||
|
||||
const isPointInsidePolygon = (
|
||||
point: [number, number],
|
||||
polygon: [number, number][]
|
||||
@@ -129,7 +130,7 @@ const DropDownList: React.FC<DropDownListProps> = ({
|
||||
title="collapse-btn"
|
||||
className="collapse-icon option"
|
||||
style={{ transform: isOpen ? "rotate(0deg)" : "rotate(-90deg)" }}
|
||||
// onClick={handleToggle}
|
||||
// onClick={handleToggle}
|
||||
>
|
||||
<ArrowIcon />
|
||||
</button>
|
||||
|
||||
@@ -17,9 +17,10 @@ import {
|
||||
useZones,
|
||||
} from "../../../store/builder/store";
|
||||
import { zoneCameraUpdate } from "../../../services/visulization/zone/zoneCameraUpdation";
|
||||
import { setFloorItemApi } from "../../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
|
||||
import { setAssetsApi } from "../../../services/factoryBuilder/assest/floorAsset/setAssetsApi";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useAssetsStore } from "../../../store/builder/useAssetStore";
|
||||
import { getUserData } from "../../../functions/getUserData";
|
||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||
|
||||
interface Asset {
|
||||
id: string;
|
||||
@@ -46,12 +47,11 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
|
||||
const { zoneAssetId, setZoneAssetId } = useZoneAssetId();
|
||||
const { zones, setZones } = useZones();
|
||||
const { setSubModule } = useSubModuleStore();
|
||||
const [expandedZones, setExpandedZones] = useState<Record<string, boolean>>(
|
||||
{}
|
||||
);
|
||||
const [expandedZones, setExpandedZones] = useState<Record<string, boolean>>({});
|
||||
const { projectId } = useParams();
|
||||
|
||||
const { setName } = useAssetsStore();
|
||||
const { assetStore } = useSceneContext();
|
||||
const { setName } = assetStore();
|
||||
const { organization } = getUserData();
|
||||
|
||||
useEffect(() => {
|
||||
useSelectedZoneStore.getState().setSelectedZone({
|
||||
@@ -81,9 +81,6 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
|
||||
|
||||
setSubModule("zoneProperties");
|
||||
|
||||
const email = localStorage.getItem("email");
|
||||
const organization = email?.split("@")[1]?.split(".")[0] ?? "";
|
||||
|
||||
let response = await getZoneData(id, organization, projectId);
|
||||
setSelectedZone({
|
||||
zoneName: response?.zoneName,
|
||||
@@ -100,14 +97,12 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleAssetClick(asset: Asset) {
|
||||
setZoneAssetId(asset);
|
||||
}
|
||||
|
||||
async function handleZoneNameChange(newName: string) {
|
||||
const email = localStorage.getItem("email") ?? "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
|
||||
const isDuplicate = zones.some(
|
||||
(zone: any) =>
|
||||
zone.zoneName?.trim().toLowerCase() === newName?.trim().toLowerCase() &&
|
||||
@@ -136,18 +131,14 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
|
||||
}
|
||||
|
||||
async function handleZoneAssetName(newName: string) {
|
||||
const email = localStorage.getItem("email") ?? "";
|
||||
const organization = email?.split("@")[1]?.split(".")[0];
|
||||
|
||||
if (zoneAssetId?.id) {
|
||||
let response = await setFloorItemApi(
|
||||
organization,
|
||||
zoneAssetId.id,
|
||||
newName,
|
||||
let response = await setAssetsApi({
|
||||
modelUuid: zoneAssetId.id,
|
||||
modelName: newName,
|
||||
projectId
|
||||
);
|
||||
});
|
||||
// console.log("response: ", response);
|
||||
console.log(' zoneAssetId.id,: ', zoneAssetId.id,);
|
||||
|
||||
setName(zoneAssetId.id, response.modelName);
|
||||
}
|
||||
@@ -207,20 +198,23 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
|
||||
id: '',
|
||||
name: '',
|
||||
});
|
||||
setSubModule("properties")
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', onMouseDown);
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
if (selectedZone.zoneName! === '' && activeModule === 'Builder') {
|
||||
document.addEventListener('mousedown', onMouseDown);
|
||||
document.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp);
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', onMouseDown);
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
};
|
||||
}, []);
|
||||
}, [selectedZone, activeModule]);
|
||||
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,12 +2,11 @@ import React, { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ArrowIcon } from "../../icons/ExportCommonIcons";
|
||||
import { toggleTheme } from "../../../utils/theme";
|
||||
import useVersionHistoryStore, {
|
||||
import useVersionHistoryVisibleStore, {
|
||||
useShortcutStore,
|
||||
useVersionStore,
|
||||
} from "../../../store/builder/store";
|
||||
import { useSubModuleStore } from "../../../store/useModuleStore";
|
||||
import { generateUniqueId } from "../../../functions/generateUniqueId";
|
||||
import useModuleStore, { useSubModuleStore } from "../../../store/useModuleStore";
|
||||
import { useVersionHistoryStore } from "../../../store/builder/useVersionHistoryStore";
|
||||
|
||||
interface MenuBarProps {
|
||||
setOpenMenu: (isOpen: boolean) => void;
|
||||
@@ -22,16 +21,15 @@ interface MenuItem {
|
||||
}
|
||||
|
||||
const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
|
||||
const userName = localStorage.getItem("userName") ?? "Anonymous";
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [activeMenu, setActiveMenu] = useState<string | null>(null);
|
||||
const [activeSubMenu, setActiveSubMenu] = useState<string | null>(null);
|
||||
const [selectedItems, setSelectedItems] = useState<Record<string, boolean>>(
|
||||
{}
|
||||
);
|
||||
const [selectedItems, setSelectedItems] = useState<Record<string, boolean>>({});
|
||||
|
||||
const { setVersionHistory } = useVersionHistoryStore();
|
||||
const { setCreateNewVersion } = useVersionHistoryStore();
|
||||
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
|
||||
const { setActiveModule } = useModuleStore();
|
||||
const { setSubModule } = useSubModuleStore();
|
||||
const { showShortcuts, setShowShortcuts } = useShortcutStore();
|
||||
|
||||
@@ -59,35 +57,21 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
|
||||
setShowShortcuts(!showShortcuts);
|
||||
}
|
||||
|
||||
function handleVersionCreation() {
|
||||
setCreateNewVersion(true);
|
||||
setVersionHistoryVisible(true);
|
||||
setSubModule("properties");
|
||||
setActiveModule('builder');
|
||||
}
|
||||
|
||||
const menus: Record<string, MenuItem[]> = {
|
||||
File: [
|
||||
{ label: "New File", shortcut: "Ctrl + N" },
|
||||
{ label: "Open Local File", shortcut: "Ctrl + O" },
|
||||
{
|
||||
label: "Save Version",
|
||||
action: () => {
|
||||
const versionStore = useVersionStore.getState();
|
||||
const versionCount = versionStore.versions.length;
|
||||
|
||||
const newVersion = {
|
||||
id: generateUniqueId(),
|
||||
versionLabel: `v${versionCount + 1}.0`,
|
||||
timestamp: `${new Date().toLocaleTimeString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
})} ${new Date().toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "2-digit",
|
||||
})}`,
|
||||
|
||||
savedBy: userName,
|
||||
};
|
||||
|
||||
console.log("newVersion: ", newVersion);
|
||||
versionStore.addVersion(newVersion);
|
||||
},
|
||||
shortcut: "Ctrl + Alt + S",
|
||||
action: handleVersionCreation,
|
||||
},
|
||||
{ label: "Make a Copy" },
|
||||
{ label: "Share" },
|
||||
@@ -236,11 +220,14 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
|
||||
setActiveSubMenu(null);
|
||||
}}
|
||||
onClick={() => {
|
||||
setVersionHistory(true);
|
||||
setVersionHistoryVisible(true);
|
||||
setSubModule("properties");
|
||||
setActiveModule('builder');
|
||||
}}
|
||||
>
|
||||
<div className="menu-button">Version history</div>
|
||||
<div className="menu-button">
|
||||
Version history <div className="value">Ctrl + H</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
{/* Theme */}
|
||||
|
||||
@@ -13,6 +13,7 @@ interface AssetDetailsCardInterface {
|
||||
enableStatue?: boolean;
|
||||
count?: number;
|
||||
totalCapacity?: number;
|
||||
activeTime?: any;
|
||||
assetDetails?: {
|
||||
assetName: string;
|
||||
const: string;
|
||||
@@ -59,6 +60,7 @@ const AssetDetailsCard: React.FC<AssetDetailsCardInterface> = ({
|
||||
status,
|
||||
count,
|
||||
totalCapacity,
|
||||
activeTime,
|
||||
assetDetails,
|
||||
}) => {
|
||||
const [moreDetails, setMoreDetails] = useState(false);
|
||||
@@ -75,7 +77,10 @@ const AssetDetailsCard: React.FC<AssetDetailsCardInterface> = ({
|
||||
<div className="content">
|
||||
<div className="name">{name}</div>
|
||||
{enableStatue && (
|
||||
<div className="status-container">{GetStatus(status)}</div>
|
||||
<>
|
||||
<div className="active-time">Active Time : <span className="value">{activeTime}</span></div>
|
||||
<div className="status-container">{GetStatus(status)}</div>
|
||||
</>
|
||||
)}
|
||||
{!enableStatue && totalCapacity && (
|
||||
<div className="storage-container">Storage/Inventory</div>
|
||||
|
||||
@@ -167,7 +167,7 @@ const SimulationPlayer: React.FC = () => {
|
||||
return (
|
||||
<>
|
||||
{isPlaying && activeModule === "simulation" && (
|
||||
<div className="label-toogler">
|
||||
<div className="label-toogler "> {/* bottom */}
|
||||
<InputToggle
|
||||
value={viewSceneLabels}
|
||||
inputKey="1"
|
||||
@@ -187,7 +187,7 @@ const SimulationPlayer: React.FC = () => {
|
||||
<div className="icon">
|
||||
<HourlySimulationIcon />
|
||||
</div>
|
||||
<div className="label">ThroughPut</div>
|
||||
<h4 className="label">ThroughPut</h4>
|
||||
</div>
|
||||
<div className="progress-wrapper">
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user