diff --git a/app/src/components/Dashboard/DashboardCard.tsx b/app/src/components/Dashboard/DashboardCard.tsx index 7dfb283..c15765e 100644 --- a/app/src/components/Dashboard/DashboardCard.tsx +++ b/app/src/components/Dashboard/DashboardCard.tsx @@ -1,7 +1,7 @@ -import React, { useState, useRef } from "react"; +import React, { useState, useRef, useCallback, useEffect } from "react"; import { createPortal } from "react-dom"; -import img from "../../assets/image/image.png"; import { useNavigate } from "react-router-dom"; +import img from "../../assets/image/image.png"; import { getUserData } from "../../functions/getUserData"; import { useLoadingProgress, @@ -16,7 +16,7 @@ import { getAllProjects } from "../../services/dashboard/getAllProjects"; interface DashBoardCardProps { projectName: string; - thumbnail: any; + thumbnail: string; projectId: string; createdAt?: string; isViewed?: string; @@ -35,13 +35,29 @@ interface DashBoardCardProps { projectName: string, thumbnail: string ) => Promise; - active?: string; + active?: "shared" | "trash" | "recent" | string; setIsSearchActive?: React.Dispatch>; - setRecentDuplicateData?: React.Dispatch>; - setProjectDuplicateData?: React.Dispatch>; + setRecentDuplicateData?: React.Dispatch>; + setProjectDuplicateData?: React.Dispatch>; setActiveFolder?: React.Dispatch>; + openKebabProjectId: string | null; + setOpenKebabProjectId: React.Dispatch>; } -type RelativeTimeFormatUnit = any; + +type RelativeTimeFormatUnit = + | "year" + | "month" + | "week" + | "day" + | "hour" + | "minute" + | "second"; + +const kebabOptionsMap: Record = { + default: ["rename", "delete", "duplicate", "open in new tab"], + trash: ["restore", "delete"], + shared: ["duplicate", "open in new tab"], +}; const DashboardCard: React.FC = ({ projectName, @@ -58,131 +74,143 @@ const DashboardCard: React.FC = ({ setRecentDuplicateData, setProjectDuplicateData, setActiveFolder, + openKebabProjectId, + setOpenKebabProjectId, }) => { const navigate = useNavigate(); const { setProjectName } = useProjectName(); const { userId, organization, userName } = getUserData(); - const [isKebabOpen, setIsKebabOpen] = useState(false); - const [renameValue, setRenameValue] = useState(projectName); - const [isRenaming, setIsRenaming] = useState(false); const { projectSocket } = useSocketStore(); const { setLoadingProgress } = useLoadingProgress(); + + const isKebabOpen = openKebabProjectId === projectId; + const [renameValue, setRenameValue] = useState(projectName); + const [isRenaming, setIsRenaming] = useState(false); const kebabRef = useRef(null); - const navigateToProject = async (e: any) => { - if (active && active === "trash") return; - try { - // const viewProjects = await viewProject(organization, projectId, userId); + // Close kebab when clicking outside + OuterClick({ + contextClassName: [`tag-${projectId}`], + setMenuVisible: () => { + if (isKebabOpen) setOpenKebabProjectId(null); + }, + }); - setLoadingProgress(1); - setProjectName(projectName); - navigate(`/projects/${projectId}`); - } catch {} - }; + const navigateToProject = useCallback(() => { + if (active === "trash") return; + setLoadingProgress(1); + setProjectName(projectName); + navigate(`/projects/${projectId}`); + }, [ + active, + projectId, + projectName, + navigate, + setLoadingProgress, + setProjectName, + ]); - const handleOptionClick = async (option: string) => { - switch (option) { - case "delete": - if (handleDeleteProject) { - handleDeleteProject(projectId); - } else if (handleTrashDeleteProject) { - handleTrashDeleteProject(projectId); + const getOptions = useCallback(() => { + if (active === "trash") return kebabOptionsMap.trash; + if (active === "shared" || (createdBy && createdBy._id !== userId)) { + return kebabOptionsMap.shared; + } + return kebabOptionsMap.default; + }, [active, createdBy, userId]); + + const handleProjectName = useCallback( + async (newName: string) => { + setRenameValue(newName); + if (!projectId) return; + + try { + const projects = await getAllProjects(userId, organization); + const projectUuid = projects?.Projects?.find( + (val: any) => val.projectUuid === projectId || val._id === projectId + ); + if (!projectUuid) return; + + const updatePayload = { + projectId: projectUuid._id, + organization, + userId, + projectName: newName, + }; + + if (projectSocket) { + projectSocket.emit("v1:project:update", updatePayload); } - break; - case "restore": - if (handleRestoreProject) { - await handleRestoreProject(projectId); - } - break; - case "open in new tab": - try { - if (active === "shared" && createdBy) { - // const newTab = await viewProject( - // organization, - // projectId, - // createdBy?._id - // ); - } else { - // const newTab = await viewProject(organization, projectId, userId); + } catch { + // silent fail + } + }, + [projectId, userId, organization, projectSocket] + ); - setProjectName(projectName); - setIsKebabOpen(false); - } - } catch (error) {} - window.open(`/projects/${projectId}`, "_blank"); - break; - case "rename": - setIsRenaming(true); - break; - case "duplicate": - if (handleDuplicateWorkspaceProject) { - setProjectDuplicateData && - setProjectDuplicateData({ + const handleOptionClick = useCallback( + async (option: string) => { + switch (option) { + case "delete": + await (handleDeleteProject?.(projectId) ?? + handleTrashDeleteProject?.(projectId)); + break; + case "restore": + await handleRestoreProject?.(projectId); + break; + case "open in new tab": + setProjectName(projectName); + window.open(`/projects/${projectId}`, "_blank"); + break; + case "rename": + setIsRenaming(true); + break; + case "duplicate": + if (handleDuplicateWorkspaceProject) { + setProjectDuplicateData?.({ projectId, projectName, thumbnail }); + await handleDuplicateWorkspaceProject( projectId, projectName, thumbnail, - }); - await handleDuplicateWorkspaceProject( - projectId, - projectName, - thumbnail, - userId - ); - if (active === "shared" && setActiveFolder) { - setActiveFolder("myProjects"); - } - } else if (handleDuplicateRecentProject) { - setRecentDuplicateData && - setRecentDuplicateData({ + userId + ); + if (active === "shared") { + setActiveFolder?.("myProjects"); + } + } else if (handleDuplicateRecentProject) { + setRecentDuplicateData?.({ projectId, projectName, thumbnail, userId, }); - await handleDuplicateRecentProject(projectId, projectName, thumbnail); - } - break; - default: - break; - } - setIsKebabOpen(false); - }; - - OuterClick({ - contextClassName: ["kebab-wrapper", "kebab-options-wrapper"], - setMenuVisible: () => setIsKebabOpen(false), - }); - - const handleProjectName = async (projectName: string) => { - setRenameValue(projectName); - if (!projectId) return; - try { - const projects = await getAllProjects(userId, organization); - if (!projects || !projects.Projects) return; - let projectUuid = projects.Projects.find( - (val: any) => val.projectUuid === projectId || val._id === projectId - ); - const updateProjects = { - projectId: projectUuid?._id, - organization, - userId, - projectName, - thumbnail: undefined, - }; - // const updatedProjectName = await updateProject( - // projectUuid._id, - // userId, - // organization, - // undefined, - // projectName - // ); - if (projectSocket) { - projectSocket.emit("v1:project:update", updateProjects); + await handleDuplicateRecentProject( + projectId, + projectName, + thumbnail + ); + } + break; } - } catch (error) {} - }; + }, + [ + projectId, + projectName, + thumbnail, + userId, + active, + handleDeleteProject, + handleTrashDeleteProject, + handleRestoreProject, + handleDuplicateWorkspaceProject, + handleDuplicateRecentProject, + setProjectName, + setProjectDuplicateData, + setRecentDuplicateData, + setActiveFolder, + ] + ); - function getRelativeTime(dateString: string): string { + const getRelativeTime = useCallback((dateString: string): string => { const date = new Date(dateString); const now = new Date(); const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); @@ -198,69 +226,56 @@ const DashboardCard: React.FC = ({ }; 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); - } + for (const [unit, seconds] of Object.entries(intervals)) { + const diff = Math.floor(diffInSeconds / seconds); + if (diff >= 1) return rtf.format(-diff, unit as RelativeTimeFormatUnit); } return "just now"; - } + }, []); - const kebabOptionsMap: Record = { - default: ["rename", "delete", "duplicate", "open in new tab"], - trash: ["restore", "delete"], - shared: ["duplicate", "open in new tab"], - }; + const [kebabPosition, setKebabPosition] = useState({ top: 0, left: 0 }); - 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; - }; + useEffect(() => { + if (isKebabOpen && kebabRef.current) { + const rect = kebabRef.current.getBoundingClientRect(); + setKebabPosition({ + top: rect.bottom + window.scrollY, + left: rect.left + window.scrollX - 80, + }); + } + }, [isKebabOpen]); return ( - + {isKebabOpen && createPortal(
{getOptions().map((option) => (
, document.body )} - + ); }; diff --git a/app/src/components/Dashboard/DashboardHome.tsx b/app/src/components/Dashboard/DashboardHome.tsx deleted file mode 100644 index 0569c08..0000000 --- a/app/src/components/Dashboard/DashboardHome.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import React, { useEffect, useState } from "react"; -import DashboardCard from "./DashboardCard"; -import DashboardNavBar from "./DashboardNavBar"; -import MarketPlaceBanner from "./MarketPlaceBanner"; -import { getUserData } from "../../functions/getUserData"; -import { useSocketStore } from "../../store/builder/store"; -import { recentlyViewed } from "../../services/dashboard/recentlyViewed"; -import { searchProject } from "../../services/dashboard/searchProjects"; -import { deleteProject } from "../../services/dashboard/deleteProject"; -import ProjectSocketRes from "./socket/projectSocketRes.dev"; -import { generateUniqueId } from "../../functions/generateUniqueId"; - -interface Project { - _id: string; - projectName: string; - thumbnail: string; - createdBy: { _id: string; userName: string }; - projectUuid?: string; - createdAt: string; - isViewed?: string; -} - -interface RecentProjectsData { - [key: string]: Project[]; -} - -const DashboardHome: React.FC = () => { - const [recentProjects, setRecentProjects] = useState({}); - - const [isSearchActive, setIsSearchActive] = useState(false); - const { userId, organization } = getUserData(); - const { projectSocket } = useSocketStore(); - const [recentDuplicateData, setRecentDuplicateData] = useState({}); - - const fetchRecentProjects = async () => { - try { - const projects = await recentlyViewed(organization, userId); - - if (JSON.stringify(projects) !== JSON.stringify(recentProjects)) { - setRecentProjects(projects); - } - } catch (error) {} - }; - - const handleRecentProjectSearch = async (inputValue: string) => { - if (!inputValue.trim()) { - setIsSearchActive(false); - return; - } - const filterRecentProcess = await searchProject( - organization, - userId, - inputValue - ); - setIsSearchActive(true); - setRecentProjects(filterRecentProcess.message ? {} : filterRecentProcess); - }; - - const handleDeleteProject = async (projectId: any) => { - try { - //API for delete project - // const deletedProject = await deleteProject( - // projectId, - // userId, - // organization - // ); - // - - //socket for delete Project - const deleteProject = { - projectId, - organization, - userId, - }; - - if (projectSocket) { - projectSocket.emit("v1:project:delete", deleteProject); - } - - setRecentProjects((prevDiscardedProjects: RecentProjectsData) => { - if (!Array.isArray(prevDiscardedProjects?.RecentlyViewed)) { - return prevDiscardedProjects; - } - const updatedProjectDatas = prevDiscardedProjects.RecentlyViewed.filter( - (project) => project._id !== projectId - ); - return { - ...prevDiscardedProjects, - RecentlyViewed: updatedProjectDatas, - }; - }); - setIsSearchActive(false); - } catch (error) {} - }; - - const handleDuplicateRecentProject = async ( - projectId: string, - projectName: string, - thumbnail: string - ) => { - const duplicateRecentProjectData = { - userId, - thumbnail, - organization, - projectUuid: generateUniqueId(), - refProjectID: projectId, - projectName, - }; - projectSocket.emit("v1:project:Duplicate", duplicateRecentProjectData); - }; - - const renderProjects = () => { - const projectList = recentProjects[Object.keys(recentProjects)[0]]; - - if (!projectList?.length) { - return
No recent projects found
; - } - - return ( - projectList && - projectList.map((project) => ( - - )) - ); - }; - - useEffect(() => { - if (!isSearchActive) { - fetchRecentProjects(); - } - }, [isSearchActive]); - - return ( -
- - -
-

Recents

-
{renderProjects()}
-
- {recentDuplicateData && Object.keys(recentDuplicateData).length > 0 && ( - - )} -
- ); -}; - -export default DashboardHome; diff --git a/app/src/components/Dashboard/DashboardMain.tsx b/app/src/components/Dashboard/DashboardMain.tsx new file mode 100644 index 0000000..c8f6ea7 --- /dev/null +++ b/app/src/components/Dashboard/DashboardMain.tsx @@ -0,0 +1,272 @@ +import React, { useEffect, useState } from "react"; +import DashboardNavBar from "./DashboardNavBar"; +import DashboardCard from "./DashboardCard"; +import MarketPlaceBanner from "./MarketPlaceBanner"; +import { getUserData } from "../../functions/getUserData"; +import { useSocketStore } from "../../store/builder/store"; +import { getAllProjects } from "../../services/dashboard/getAllProjects"; +import { sharedWithMeProjects } from "../../services/dashboard/sharedWithMeProject"; +import { recentlyViewed } from "../../services/dashboard/recentlyViewed"; +import { getTrash } from "../../services/dashboard/getTrash"; +import { searchProject } from "../../services/dashboard/searchProjects"; +import { trashSearchProject } from "../../services/dashboard/trashSearchProject"; +import { restoreTrash } from "../../services/dashboard/restoreTrash"; +import { generateUniqueId } from "../../functions/generateUniqueId"; +import ProjectSocketRes from "./socket/projectSocketRes"; + +interface Project { + _id: string; + projectName: string; + thumbnail: string; + createdBy: { _id: string; userName: string }; + projectUuid?: string; + createdAt?: string; + DeletedAt?: string; +} + +interface ProjectCollection { + [key: string]: Project[]; +} + +type Folder = "home" | "projects" | "shared" | "trash"; + +interface DashboardMainProps { + activeFolder: Folder; +} + +const DashboardMain: React.FC = ({ activeFolder }) => { + const [activeSubFolder, setActiveSubFolder] = useState("myProjects"); + const [projectsData, setProjectsData] = useState({}); + const [isSearchActive, setIsSearchActive] = useState(false); + const [duplicateData, setDuplicateData] = useState({}); + const [openKebabProjectId, setOpenKebabProjectId] = useState( + null + ); + const [projectsCache, setProjectsCache] = useState<{ + [key: string]: ProjectCollection; + }>({}); + + const { userId, organization } = getUserData(); + const { projectSocket } = useSocketStore(); + + // #region API Fetchers + const fetchData = async () => { + const cacheKey = + activeFolder + (activeFolder === "projects" ? `-${activeSubFolder}` : ""); + + if (projectsCache[cacheKey] && !isSearchActive) { + setProjectsData(projectsCache[cacheKey]); + return; + } + + try { + let projects: ProjectCollection = {}; // initialize as empty object + + switch (activeFolder) { + case "home": + projects = await recentlyViewed(organization, userId); + break; + case "projects": + if (activeSubFolder === "myProjects") { + projects = await getAllProjects(userId, organization); + } else { + projects = await sharedWithMeProjects(); + } + break; + case "trash": + projects = await getTrash(organization); + break; + } + + // Only update cache if projects is not empty + if ( + projects && + JSON.stringify(projects) !== JSON.stringify(projectsData) + ) { + setProjectsCache((prev) => ({ ...prev, [cacheKey]: projects })); + setProjectsData(projects); + } + } catch (error) { + console.error(error); + } + }; + + // #region Search Handlers + const handleSearch = async (inputValue: string) => { + if (!inputValue.trim()) { + setIsSearchActive(false); + return; + } + + let results; + if (activeFolder === "trash") { + results = await trashSearchProject(organization, userId, inputValue); + } else { + results = await searchProject(organization, userId, inputValue); + } + + setIsSearchActive(true); + setProjectsData(results?.message ? {} : results); + }; + + // #region Socket Actions + const handleDelete = async (projectId: string): Promise => { + if (projectSocket) { + projectSocket.emit("v1:project:delete", { + projectId, + organization, + userId, + }); + } + updateStateAfterRemove(projectId); + }; + + const handlePermanentDelete = async (projectId: string): Promise => { + if (projectSocket) { + projectSocket.emit("v1:trash:delete", { + projectId, + organization, + userId, + }); + } + updateStateAfterRemove(projectId); + }; + + const handleRestore = async (projectId: string): Promise => { + await restoreTrash(organization, projectId); + updateStateAfterRemove(projectId); + }; + + const handleDuplicate = async ( + projectId: string, + projectName: string, + thumbnail: string + ): Promise => { + if (projectSocket) { + projectSocket.emit("v1:project:Duplicate", { + userId, + thumbnail, + organization, + projectUuid: generateUniqueId(), + refProjectID: projectId, + projectName, + }); + } + }; + + // #region Project Map + const updateStateAfterRemove = (projectId: string) => { + setProjectsData((prev: ProjectCollection) => { + const key = Object.keys(prev)[0]; + const updatedList = prev[key]?.filter((p) => p._id !== projectId) || []; + return { ...prev, [key]: updatedList }; + }); + setIsSearchActive(false); + }; + + const renderProjects = () => { + const key = Object.keys(projectsData)[0]; + const projectList = projectsData[key]; + + if (!projectList?.length) { + return
No projects found
; + } + + return projectList.map((project) => ( + + )); + }; + + // #region Use Effects + useEffect(() => { + if (!isSearchActive) fetchData(); + // eslint-disable-next-line + }, [activeFolder, isSearchActive, activeSubFolder]); + + return ( +
+ + + {activeFolder === "home" && } + +
+ {activeFolder === "projects" && ( +
+ + +
+ )} + +
{renderProjects()}
+ + {duplicateData && Object.keys(duplicateData).length > 0 && ( + + )} +
+
+ ); +}; + +export default DashboardMain; diff --git a/app/src/components/Dashboard/DashboardNavBar.tsx b/app/src/components/Dashboard/DashboardNavBar.tsx index c86ef69..7ebec4d 100644 --- a/app/src/components/Dashboard/DashboardNavBar.tsx +++ b/app/src/components/Dashboard/DashboardNavBar.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback, useMemo } from "react"; import Search from "../ui/inputs/Search"; import { CartIcon } from "../icons/ExportModuleIcons"; @@ -15,19 +15,23 @@ const DashboardNavBar: React.FC = ({ handleTrashSearch, handleRecentProjectSearch, }) => { - const handleSearch = async (inputValue: string) => { - try { - if (handleProjectsSearch) { - await handleProjectsSearch(inputValue); - } else if (handleTrashSearch) { - await handleTrashSearch(inputValue); - } else if (handleRecentProjectSearch) { - await handleRecentProjectSearch(inputValue); + // Determine active search handler + const activeSearchHandler = useMemo( + () => handleProjectsSearch || handleTrashSearch || handleRecentProjectSearch, + [handleProjectsSearch, handleTrashSearch, handleRecentProjectSearch] + ); + + const handleSearch = useCallback( + async (inputValue: string) => { + if (!activeSearchHandler) return; + try { + await activeSearchHandler(inputValue); + } catch (error) { + console.error("Search failed:", error); } - } catch (error) { - console.error("Search failed:", error); - } - }; + }, + [activeSearchHandler] + ); return (
@@ -40,4 +44,4 @@ const DashboardNavBar: React.FC = ({ ); }; -export default DashboardNavBar; \ No newline at end of file +export default DashboardNavBar; diff --git a/app/src/components/Dashboard/DashboardProjects.tsx b/app/src/components/Dashboard/DashboardProjects.tsx deleted file mode 100644 index ba2db7b..0000000 --- a/app/src/components/Dashboard/DashboardProjects.tsx +++ /dev/null @@ -1,227 +0,0 @@ -import React, { useEffect, useState } from "react"; -import DashboardNavBar from "./DashboardNavBar"; -import DashboardCard from "./DashboardCard"; -import { getUserData } from "../../functions/getUserData"; -import { useSocketStore } from "../../store/builder/store"; -import { getAllProjects } from "../../services/dashboard/getAllProjects"; -import { searchProject } from "../../services/dashboard/searchProjects"; -import { deleteProject } from "../../services/dashboard/deleteProject"; -import ProjectSocketRes from "./socket/projectSocketRes.dev"; -import { sharedWithMeProjects } from "../../services/dashboard/sharedWithMeProject"; -import { duplicateProject } from "../../services/dashboard/duplicateProject"; -import { generateUniqueId } from "../../functions/generateUniqueId"; - -interface Project { - _id: string; - projectName: string; - thumbnail: string; - createdBy: string; - projectUuid?: string; - createdAt: string; -} - -interface WorkspaceProjects { - [key: string]: Project[]; -} - -const DashboardProjects: React.FC = () => { - const [workspaceProjects, setWorkspaceProjects] = useState( - {} - ); - const [sharedwithMeProject, setSharedWithMeProjects] = useState([]); - const [projectDuplicateData, setProjectDuplicateData] = useState({}); - const [isSearchActive, setIsSearchActive] = useState(false); - const [activeFolder, setActiveFolder] = useState("myProjects"); - const { projectSocket } = useSocketStore(); - const { userId, organization } = getUserData(); - - const handleProjectsSearch = async (inputValue: string) => { - if (!inputValue.trim()) { - setIsSearchActive(false); - return; - } - if (!setWorkspaceProjects || !setIsSearchActive) return; - - const searchedProject = await searchProject( - organization, - userId, - inputValue - ); - setIsSearchActive(true); - setWorkspaceProjects(searchedProject.message ? {} : searchedProject); - }; - - const fetchAllProjects = async () => { - try { - const projects = await getAllProjects(userId, organization); - - if (!projects || !projects.Projects) return; - - if (JSON.stringify(projects) !== JSON.stringify(workspaceProjects)) { - setWorkspaceProjects(projects); - } - } catch (error) {} - }; - - const handleDeleteProject = async (projectId: any) => { - try { - // const deletedProject = await deleteProject( - // projectId, - // userId, - // organization - // ); - // - const deleteProjects = { - projectId, - organization, - userId, - }; - - //socket for deleting the project - if (projectSocket) { - projectSocket.emit("v1:project:delete", deleteProjects); - } else { - } - setWorkspaceProjects((prevDiscardedProjects: WorkspaceProjects) => { - if (!Array.isArray(prevDiscardedProjects?.Projects)) { - return prevDiscardedProjects; - } - const updatedProjectDatas = prevDiscardedProjects.Projects.filter( - (project) => project._id !== projectId - ); - return { - ...prevDiscardedProjects, - Projects: updatedProjectDatas, - }; - }); - setIsSearchActive(false); - } catch (error) {} - }; - - const handleDuplicateWorkspaceProject = async ( - projectId: string, - projectName: string, - thumbnail: string - ) => { - // const duplicatedProject = await duplicateProject( - // projectId, - // generateUniqueId(), - // thumbnail, - // projectName - // ); - // console.log("duplicatedProject: ", duplicatedProject); - - const duplicateProjectData = { - userId, - thumbnail, - organization, - projectUuid: generateUniqueId(), - refProjectID: projectId, - projectName, - }; - projectSocket.emit("v1:project:Duplicate", duplicateProjectData); - }; - - const renderProjects = () => { - if (activeFolder !== "myProjects") return null; - const projectList = workspaceProjects[Object.keys(workspaceProjects)[0]]; - if (!projectList?.length) { - return
No projects found
; - } - - return projectList.map((project) => ( - - )); - }; - - const renderSharedProjects = () => { - return sharedwithMeProject?.map((project: any) => ( - - )); - }; - - const sharedProject = async () => { - try { - const sharedWithMe = await sharedWithMeProjects(); - setSharedWithMeProjects(sharedWithMe); - } catch {} - }; - - useEffect(() => { - if (!isSearchActive) { - fetchAllProjects(); - } - }, [isSearchActive]); - - useEffect(() => { - if (activeFolder === "shared") { - sharedProject(); - } - }, [activeFolder]); - - return ( -
- - -
-
- - -
-
- {activeFolder == "myProjects" - ? renderProjects() - : renderSharedProjects()} -
- - {projectDuplicateData && - Object.keys(projectDuplicateData).length > 0 && ( - - )} -
-
- ); -}; - -export default DashboardProjects; diff --git a/app/src/components/Dashboard/DashboardTrash.tsx b/app/src/components/Dashboard/DashboardTrash.tsx deleted file mode 100644 index 8bac359..0000000 --- a/app/src/components/Dashboard/DashboardTrash.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import React, { useEffect, useState } from "react"; -import DashboardCard from "./DashboardCard"; -import DashboardNavBar from "./DashboardNavBar"; -import { getUserData } from "../../functions/getUserData"; -import { trashSearchProject } from "../../services/dashboard/trashSearchProject"; -import { restoreTrash } from "../../services/dashboard/restoreTrash"; -import { getTrash } from "../../services/dashboard/getTrash"; -import { deleteTrash } from "../../services/dashboard/deleteTrash"; -import { useSocketStore } from "../../store/builder/store"; - -interface Project { - _id: string; - projectName: string; - thumbnail: string; - createdBy: string; - projectUuid?: string; - DeletedAt: string; -} - -interface DiscardedProjects { - [key: string]: Project[]; -} - -const DashboardTrash: React.FC = () => { - const [discardedProjects, setDiscardedProjects] = useState({}); - const [isSearchActive, setIsSearchActive] = useState(false); - const { userId, organization } = getUserData(); - const { projectSocket } = useSocketStore(); - - const fetchTrashProjects = async () => { - try { - const projects = await getTrash(organization); - - if (JSON.stringify(projects) !== JSON.stringify(discardedProjects)) { - setDiscardedProjects(projects); - } - } catch (error) { - console.error("Error fetching trash projects:", error); - } - }; - - const handleTrashSearch = async (inputValue: string) => { - if (!inputValue.trim()) { - setIsSearchActive(false); - return; - } - if (!setDiscardedProjects || !setIsSearchActive) return; - - const filterTrashedProcess = await trashSearchProject( - organization, - userId, - inputValue - ); - setIsSearchActive(true); - setDiscardedProjects( - filterTrashedProcess.message ? {} : filterTrashedProcess - ); - }; - - const handleRestoreProject = async (projectId: any) => { - try { - const restoreProject = await restoreTrash(organization, projectId); - // console.log('restoreProject: ', restoreProject); - - setDiscardedProjects((prevDiscardedProjects: DiscardedProjects) => { - // Check if TrashDatas exists and is an array - if (!Array.isArray(prevDiscardedProjects?.TrashDatas)) { - console.error("TrashDatas is not an array", prevDiscardedProjects); - return prevDiscardedProjects; - } - const updatedTrashDatas = prevDiscardedProjects.TrashDatas.filter( - (project) => project._id !== projectId - ); - return { - ...prevDiscardedProjects, - TrashDatas: updatedTrashDatas, - }; - }); - setIsSearchActive(false); - } catch (error) { - console.error("Error deleting project:", error); - } - }; - - const handleTrashDeleteProject = async (projectId: any) => { - try { - // const deletedProject = await deleteTrash( - // organization, projectId - // ); - - const deleteProjectTrash = { - projectId, - organization, - userId, - }; - if (projectSocket) { - projectSocket.emit("v1:trash:delete", deleteProjectTrash); - } - setDiscardedProjects((prevDiscardedProjects: DiscardedProjects) => { - if (!Array.isArray(prevDiscardedProjects?.TrashDatas)) { - return prevDiscardedProjects; - } - const updatedProjectDatas = prevDiscardedProjects.TrashDatas.filter( - (project) => project._id !== projectId - ); - // console.log('updatedProjectDatas: ', updatedProjectDatas); - return { - ...prevDiscardedProjects, - TrashDatas: updatedProjectDatas, - }; - }); - setIsSearchActive(false); - } catch (error) { - console.error("Error deleting project:", error); - } - }; - - const renderTrashProjects = () => { - const projectList = discardedProjects[Object.keys(discardedProjects)[0]]; - - if (!projectList?.length) { - return
No deleted projects found
; - } - - return projectList.map((project) => ( - - )); - }; - - useEffect(() => { - if (!isSearchActive) { - fetchTrashProjects(); - } - }, [isSearchActive]); - - return ( -
- - -
-
-
{renderTrashProjects()}
-
-
- ); -}; - -export default DashboardTrash; \ No newline at end of file diff --git a/app/src/components/Dashboard/DashboardTutorial.tsx b/app/src/components/Dashboard/DashboardTutorial.tsx index dedcccf..5a06130 100644 --- a/app/src/components/Dashboard/DashboardTutorial.tsx +++ b/app/src/components/Dashboard/DashboardTutorial.tsx @@ -1,62 +1,67 @@ -import React, { useEffect, useState } from 'react'; -import DashboardNavBar from './DashboardNavBar'; -import DashboardCard from './DashboardCard'; -import { projectTutorial } from '../../services/dashboard/projectTutorial'; +import React, { useEffect, useState } from "react"; +import DashboardNavBar from "./DashboardNavBar"; +import DashboardCard from "./DashboardCard"; +import { projectTutorial } from "../../services/dashboard/projectTutorial"; + interface Project { - _id: string; - projectName: string; - thumbnail: string; - createdBy: string; - projectUuid?: string; + _id: string; + projectName: string; + thumbnail: string; + createdBy: string; + projectUuid?: string; } interface DiscardedProjects { - [key: string]: Project[]; + [key: string]: Project[]; } const DashboardTutorial = () => { - const [tutorialProject, setTutorialProject] = useState({}) - const handleIcon = async () => { - try { - let tutorial = await projectTutorial() - setTutorialProject(tutorial) - } catch { + const [tutorialProject, setTutorialProject] = useState({}); + const handleIcon = async () => { + try { + let tutorial = await projectTutorial(); + setTutorialProject(tutorial); + } catch {} + }; - } + const [openKebabProjectId, setOpenKebabProjectId] = useState( + null + ); + + useEffect(() => { + handleIcon(); + }, []); + const renderTrashProjects = () => { + const projectList = tutorialProject[Object.keys(tutorialProject)[0]]; + + if (!projectList?.length) { + return
No deleted projects found
; } - useEffect(() => { - handleIcon() - }, []) - const renderTrashProjects = () => { - const projectList = tutorialProject[Object.keys(tutorialProject)[0]]; - if (!projectList?.length) { - return
No deleted projects found
; - } + return projectList.map((tutorials: any) => ( + + )); + }; + return ( +
+ - return projectList.map((tutorials: any) => ( - - )); - }; - return ( -
- - -
-
-
- {renderTrashProjects()} -
-
-
- ); -} +
+
+
{renderTrashProjects()}
+
+
+ ); +}; export default DashboardTutorial; diff --git a/app/src/components/Dashboard/MarketPlaceBanner.tsx b/app/src/components/Dashboard/MarketPlaceBanner.tsx index 68b631a..7480538 100644 --- a/app/src/components/Dashboard/MarketPlaceBanner.tsx +++ b/app/src/components/Dashboard/MarketPlaceBanner.tsx @@ -1,4 +1,3 @@ -import React from "react"; import banner from "../../assets/image/banner.png"; const MarketPlaceBanner = () => { diff --git a/app/src/components/Dashboard/SidePannel.tsx b/app/src/components/Dashboard/SidePannel.tsx index 2c0ad64..b612ba6 100644 --- a/app/src/components/Dashboard/SidePannel.tsx +++ b/app/src/components/Dashboard/SidePannel.tsx @@ -1,12 +1,12 @@ import React from "react"; import { - DocumentationIcon, - HelpIcon, - HomeIcon, - LogoutIcon, - NotificationIcon, - ProjectsIcon, - TutorialsIcon, + DocumentationIcon, + HelpIcon, + HomeIcon, + LogoutIcon, + NotificationIcon, + ProjectsIcon, + TutorialsIcon, } from "../icons/DashboardIcon"; import { useNavigate } from "react-router-dom"; import darkThemeImage from "../../assets/image/darkThemeProject.png"; @@ -15,180 +15,174 @@ import { SettingsIcon, TrashIcon } from "../icons/ExportCommonIcons"; import { getUserData } from "../../functions/getUserData"; import { useLoadingProgress, useSocketStore } from "../../store/builder/store"; +// import { createProject } from "../../services/dashboard/createProject"; + interface SidePannelProps { - setActiveTab: React.Dispatch>; - activeTab: string; + setActiveTab: React.Dispatch>; + activeTab: string; } const SidePannel: React.FC = ({ setActiveTab, activeTab }) => { - const { email, userName, userId, organization } = getUserData(); - const navigate = useNavigate(); - const { setLoadingProgress } = useLoadingProgress(); - const { projectSocket } = useSocketStore(); - const savedTheme = localStorage.getItem("theme") ?? "light"; + const { email, userName, userId, organization } = getUserData(); + const navigate = useNavigate(); + const { setLoadingProgress } = useLoadingProgress(); + const { projectSocket, initializeSocket } = useSocketStore(); + const savedTheme = localStorage.getItem("theme") ?? "light"; - function generateProjectId() { - const randomBytes = new Uint8Array(12); - crypto.getRandomValues(randomBytes); - return Array.from(randomBytes, (byte) => - byte.toString(16).padStart(2, "0") - ).join(""); - } - - const handleCreateNewProject = async () => { - const token = localStorage.getItem("token"); - const refreshToken = localStorage.getItem("refreshToken"); - console.log("refreshToken: ", refreshToken); - try { - const projectId = generateProjectId(); - useSocketStore - .getState() - .initializeSocket(email, organization, token, refreshToken); - - //API for creating new Project - // const project = await createProject( - // projectId, - // userId, - // savedTheme === "dark" ? darkThemeImage : lightThemeImage, - // organization - // ); - - const addProject = { - userId, - thumbnail: savedTheme === "dark" ? darkThemeImage : lightThemeImage, - organization: organization, - projectUuid: projectId, - }; - - console.log("projectSocket: ", projectSocket); - if (projectSocket) { - const handleResponse = (data: any) => { - if (data.message === "Project created successfully") { - setLoadingProgress(1); - navigate(`/projects/${data.data.projectId}`); - } - projectSocket.off("v1-project:response:add", handleResponse); // Clean up - }; - projectSocket.on("v1-project:response:add", handleResponse); - - projectSocket.emit("v1:project:add", addProject); - } else { - console.error("Socket is not connected."); - } - } catch (error) { - console.error("Error creating project:", error); + function generateProjectId() { + const randomBytes = new Uint8Array(12); + crypto.getRandomValues(randomBytes); + return Array.from(randomBytes, (byte) => + byte.toString(16).padStart(2, "0") + ).join(""); } - }; - return ( -
-
-
-
- {userName?.charAt(0).toUpperCase()} -
-
- {userName - ? userName.charAt(0).toUpperCase() + - userName.slice(1).toLowerCase() - : "Anonymous"} -
+ const handleCreateNewProject = async () => { + const token = localStorage.getItem("token"); + const refreshToken = localStorage.getItem("refreshToken"); + if (!token || !refreshToken) { + console.error('token expired'); + return; + } + + const projectId = generateProjectId(); + initializeSocket(email, organization, token, refreshToken); + + if (projectSocket?.connected) { + // SOCKET + const addProject = { + userId, + thumbnail: savedTheme === "dark" ? darkThemeImage : lightThemeImage, + organization: organization, + projectUuid: projectId, + }; + const handleResponse = (data: any) => { + if (data.message === "Project created successfully") { + setLoadingProgress(1); + navigate(`/projects/${data.data.projectId}`); + } + projectSocket.off("v1-project:response:add", handleResponse); + }; + projectSocket.on("v1-project:response:add", handleResponse); + + projectSocket.emit("v1:project:add", addProject); + } else { + // API + // const project = await createProject( + // projectId, + // userId, + // savedTheme === "dark" ? darkThemeImage : lightThemeImage, + // organization + // ); + } + }; + + return ( +
+
+
+
+ {userName?.charAt(0).toUpperCase()} +
+
+ {userName ? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase() : "Anonymous"} +
+
+
+ +
+
+
+ + New project +
+
+
+ + + + + +
+
+ + + +
+
-
- -
-
-
- + New project -
-
-
-
setActiveTab("Home")} - > - - Home -
-
setActiveTab("Projects")} - > - - Projects -
-
setActiveTab("Trash")} - > - - Trash -
-
{ - // setActiveTab("Tutorials"); - console.warn("Tutorials comming soon"); - }} - > - - Tutorials -
-
{ - // setActiveTab("Documentation"); - console.warn("Documentation comming soon"); - }} - > - - Documentation -
-
-
-
- - Settings -
-
{ - localStorage.clear(); - navigate("/"); - }} - > - - Log out -
-
- - Help & Feedback -
-
-
-
- ); + ); }; export default SidePannel; diff --git a/app/src/components/Dashboard/socket/projectSocketRes.dev.tsx b/app/src/components/Dashboard/socket/projectSocketRes.dev.tsx deleted file mode 100644 index 9fac261..0000000 --- a/app/src/components/Dashboard/socket/projectSocketRes.dev.tsx +++ /dev/null @@ -1,101 +0,0 @@ - -import React, { useEffect } from 'react'; -import { useSocketStore } from '../../../store/builder/store'; -import { getUserData } from '../../../functions/getUserData'; -import { getAllProjects } from '../../../services/dashboard/getAllProjects'; -import { recentlyViewed } from '../../../services/dashboard/recentlyViewed'; - -interface Project { - _id: string; - projectName: string; - thumbnail: string; - createdBy: string; - projectUuid?: string; - createdAt: string; - isViewed?: string -} -interface RecentProject { - _id: string; - projectName: string; - thumbnail: string; - createdBy: { _id: string, userName: string }; - projectUuid?: string; - createdAt: string; - isViewed?: string -} - -interface RecentProjectData { - [key: string]: RecentProject[]; -} -interface ProjectsData { - [key: string]: Project[]; -} -interface ProjectSocketResProps { - setRecentProjects?: React.Dispatch>; - setWorkspaceProjects?: React.Dispatch>; - setIsSearchActive?: React.Dispatch>; -} - -const ProjectSocketRes = ({ - setRecentProjects, - setWorkspaceProjects, - setIsSearchActive, -}: ProjectSocketResProps) => { - const { projectSocket } = useSocketStore(); - const { userId, organization } = getUserData(); - - useEffect(() => { - if (!projectSocket) return; - - const handleAdd = (data: any) => { - // console.log('Add:', data); - }; - - const handleDelete = (data: any) => { - // console.log('Delete:', data); - }; - - const handleUpdate = (data: any) => { - // console.log('Update:', data); - }; - - const handleTrashDelete = (data: any) => { - // console.log('Trash Delete:', data); - }; - - const handleDuplicate = async (data: any) => { - console.log("Project duplicate response:", data); - if (data?.message === "Project Duplicated successfully") { - if (setWorkspaceProjects) { - const allProjects = await getAllProjects(userId, organization); - // console.log('allProjects: ', allProjects); - setWorkspaceProjects(allProjects); - } else if (setRecentProjects) { - const recentProjects = await recentlyViewed(organization, userId); - setRecentProjects && setRecentProjects(recentProjects); - } - setIsSearchActive && setIsSearchActive(false); - } else { - console.warn("Duplication failed or unexpected response."); - } - }; - - projectSocket.on("v1-project:response:add", handleAdd); - projectSocket.on("v1-project:response:delete", handleDelete); - projectSocket.on("v1-project:response:update", handleUpdate); - projectSocket.on("v1-project:response:Duplicate", handleDuplicate); - projectSocket.on("v1:trash:response:delete", handleTrashDelete); - - return () => { - projectSocket.off("v1-project:response:add", handleAdd); - projectSocket.off("v1-project:response:delete", handleDelete); - projectSocket.off("v1-project:response:update", handleUpdate); - projectSocket.off("v1-project:response:Duplicate", handleDuplicate); - projectSocket.off("v1:trash:response:delete", handleTrashDelete); - }; - }, [projectSocket, userId, organization]); - - return null; -}; - -export default ProjectSocketRes; diff --git a/app/src/components/Dashboard/socket/projectSocketRes.tsx b/app/src/components/Dashboard/socket/projectSocketRes.tsx new file mode 100644 index 0000000..49285c2 --- /dev/null +++ b/app/src/components/Dashboard/socket/projectSocketRes.tsx @@ -0,0 +1,90 @@ +import React, { useEffect } from "react"; +import { useSocketStore } from "../../../store/builder/store"; +import { getUserData } from "../../../functions/getUserData"; +import { getAllProjects } from "../../../services/dashboard/getAllProjects"; +import { recentlyViewed } from "../../../services/dashboard/recentlyViewed"; + +interface Project { + _id: string; + projectName: string; + thumbnail: string; + createdBy: { _id: string; userName: string }; + projectUuid?: string; + createdAt?: string; + DeletedAt?: string; + isViewed?: string; +} + +interface ProjectCollection { + [key: string]: Project[]; +} + +interface ProjectSocketResProps { + setRecentProjects?: React.Dispatch>; + setWorkspaceProjects?: React.Dispatch>; + setIsSearchActive?: React.Dispatch>; +} + +const ProjectSocketRes = ({ + setRecentProjects, + setWorkspaceProjects, + setIsSearchActive, +}: ProjectSocketResProps) => { + const { projectSocket } = useSocketStore(); + const { userId, organization } = getUserData(); + + useEffect(() => { + if (!projectSocket) return; + + const handleAdd = (data: any) => { + // console.log("Add:", data); + }; + + const handleDelete = (data: any) => { + // console.log("Delete:", data); + }; + + const handleUpdate = (data: any) => { + // console.log("Update:", data); + }; + + const handleTrashDelete = (data: any) => { + // console.log("Trash Delete:", data); + }; + + const handleDuplicate = async (data: any) => { + console.log("Project duplicate response:", data); + if (data?.message === "Project Duplicated successfully") { + if (setWorkspaceProjects) { + const allProjects = await getAllProjects(userId, organization); + setWorkspaceProjects(allProjects); + } else if (setRecentProjects) { + const recentProjects = await recentlyViewed(organization, userId); + setRecentProjects(recentProjects); + } + setIsSearchActive && setIsSearchActive(false); + } else { + console.warn("Duplication failed or unexpected response."); + } + }; + + projectSocket.on("v1-project:response:add", handleAdd); + projectSocket.on("v1-project:response:delete", handleDelete); + projectSocket.on("v1-project:response:update", handleUpdate); + projectSocket.on("v1-project:response:Duplicate", handleDuplicate); + projectSocket.on("v1:trash:response:delete", handleTrashDelete); + + return () => { + projectSocket.off("v1-project:response:add", handleAdd); + projectSocket.off("v1-project:response:delete", handleDelete); + projectSocket.off("v1-project:response:update", handleUpdate); + projectSocket.off("v1-project:response:Duplicate", handleDuplicate); + projectSocket.off("v1:trash:response:delete", handleTrashDelete); + }; + // eslint-disable-next-line + }, [projectSocket, userId, organization]); + + return null; +}; + +export default ProjectSocketRes; diff --git a/app/src/components/footer/Footer.tsx b/app/src/components/footer/Footer.tsx index 39273df..daa4741 100644 --- a/app/src/components/footer/Footer.tsx +++ b/app/src/components/footer/Footer.tsx @@ -1,16 +1,10 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useMemo, useCallback } from "react"; import { HelpIcon, WifiIcon } from "../icons/DashboardIcon"; import { useLogger } from "../ui/log/LoggerContext"; import { GetLogIcon } from "./getLogIcons"; -import { - CurserLeftIcon, - CurserMiddleIcon, - CurserRightIcon, -} from "../icons/LogIcons"; +import { CurserLeftIcon, CurserMiddleIcon, CurserRightIcon } from "../icons/LogIcons"; import ShortcutHelper from "./shortcutHelper"; -import useVersionHistoryVisibleStore, { - useShortcutStore, -} from "../../store/builder/store"; +import useVersionHistoryVisibleStore, { useShortcutStore } from "../../store/builder/store"; import { usePlayButtonStore } from "../../store/usePlayButtonStore"; import useModuleStore, { useSubModuleStore } from "../../store/useModuleStore"; import { useVersionContext } from "../../modules/builder/version/versionContext"; @@ -19,7 +13,7 @@ import { useMouseNoteStore } from "../../store/useUIToggleStore"; const Footer: React.FC = () => { const { logs, setIsLogListVisible } = useLogger(); - const lastLog = logs.length > 0 ? logs[logs.length - 1] : null; + const lastLog = logs[logs.length - 1] || null; const { setActiveModule } = useModuleStore(); const { setSubModule } = useSubModuleStore(); @@ -30,46 +24,40 @@ const Footer: React.FC = () => { const { selectedVersion } = selectedVersionStore(); const { Leftnote, Middlenote, Rightnote } = useMouseNoteStore(); - const [isOnline, setIsOnline] = useState(navigator.onLine); - useEffect(() => { - const handleOnline = () => { - echo.success('You are back Online'); - setIsOnline(true); - }; - const handleOffline = () => { - echo.warn('Changes made now might not be saved'); - echo.error('You are now Offline.'); - setIsOnline(false); - }; + // -------------------- Online/Offline Handlers -------------------- + const handleOnline = useCallback(() => { + echo.success("You are back Online"); + setIsOnline(true); + }, []); + const handleOffline = useCallback(() => { + echo.warn("Changes made now might not be saved"); + echo.error("You are now Offline."); + setIsOnline(false); + }, []); + + useEffect(() => { window.addEventListener("online", handleOnline); window.addEventListener("offline", handleOffline); - return () => { window.removeEventListener("online", handleOnline); window.removeEventListener("offline", handleOffline); }; - }, []); - const mouseButtons = [ - { - icon: , - label: Leftnote !== "" ? Leftnote : "Pan", - mouse: "left", - }, - { - icon: , - label: Middlenote !== "" ? Middlenote : "Scroll Zoom", - mouse: "middle", - }, - { - icon: , - label: Rightnote !== "" ? Rightnote : "Orbit / Cancel action", - mouse: "right", - }, - ]; + }, [handleOnline, handleOffline]); + // -------------------- Mouse Buttons -------------------- + const mouseButtons = useMemo( + () => [ + { icon: , label: Leftnote || "Pan", mouse: "left" }, + { icon: , label: Middlenote || "Scroll Zoom", mouse: "middle" }, + { icon: , label: Rightnote || "Orbit / Cancel action", mouse: "right" }, + ], + [Leftnote, Middlenote, Rightnote] + ); + + // -------------------- Mouse Helper -------------------- useEffect(() => { const cleanup = mouseActionHelper(); return () => cleanup(); @@ -78,6 +66,7 @@ const Footer: React.FC = () => { return (
+ {/* Mouse Button Info */}
{mouseButtons.map(({ icon, label, mouse }) => (
@@ -87,13 +76,15 @@ const Footer: React.FC = () => { ))}
+ {/* Logs and Version */}
-
-
+
+
+
+
{ @@ -119,11 +111,8 @@ const Footer: React.FC = () => {
-
+ +
@@ -132,12 +121,9 @@ const Footer: React.FC = () => {
- {!isPlaying && ( -
+ {/* Shortcut Helper */} + {!isPlaying && showShortcuts && ( +
)} diff --git a/app/src/components/icons/DashboardIcon.tsx b/app/src/components/icons/DashboardIcon.tsx index 3eb5301..dbcfb7b 100644 --- a/app/src/components/icons/DashboardIcon.tsx +++ b/app/src/components/icons/DashboardIcon.tsx @@ -34,7 +34,7 @@ export function HomeIcon() { > ); @@ -51,7 +51,7 @@ export function ProjectsIcon() { > ); @@ -70,103 +70,103 @@ export function TutorialsIcon() { cx="8.157" cy="8.35866" r="6.17928" - stroke="var(--text-button-color)" + stroke="var(--text-color)" strokeWidth="0.562865" /> @@ -184,12 +184,12 @@ export function DocumentationIcon() { > @@ -208,7 +208,7 @@ export function HelpIcon() { @@ -236,17 +236,17 @@ export function LogoutIcon() { > diff --git a/app/src/components/layout/scenes/MainScene.tsx b/app/src/components/layout/scenes/MainScene.tsx index ef53a50..8076ec2 100644 --- a/app/src/components/layout/scenes/MainScene.tsx +++ b/app/src/components/layout/scenes/MainScene.tsx @@ -1,17 +1,5 @@ -import React, { useEffect } from "react"; -import { - useLoadingProgress, - useRenameModeStore, - useSaveVersion, - useSelectedComment, - useSocketStore, - useWidgetSubOption, -} from "../../../store/builder/store"; -import useModuleStore, { useThreeDStore } from "../../../store/useModuleStore"; -import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; -import { useSelectedZoneStore } from "../../../store/visualization/useZoneStore"; -import { useFloatingWidget } from "../../../store/visualization/useDroppedObjectsStore"; -import { useSelectedUserStore } from "../../../store/collaboration/useCollabStore"; +import { useEffect } from "react"; +import { useLoadingProgress, useRenameModeStore, useSaveVersion, useSelectedComment, useSocketStore, useWidgetSubOption } from "../../../store/builder/store"; import KeyPressListener from "../../../utils/shortcutkeys/handleShortcutKeys"; import LoadingPage from "../../templates/LoadingPage"; import ModuleToggle from "../../ui/ModuleToggle"; @@ -23,21 +11,29 @@ import Tools from "../../ui/Tools"; import SimulationPlayer from "../../ui/simulation/simulationPlayer"; import ControlsPlayer from "../controls/ControlsPlayer"; import SelectFloorPlan from "../../temporary/SelectFloorPlan"; -import { createHandleDrop } from "../../../modules/visualization/functions/handleUiDrop"; -import Scene from "../../../modules/scene/scene"; -import { useComparisonProduct, useMainProduct } from "../../../store/simulation/useSimulationStore"; -import { useProductContext } from "../../../modules/simulation/products/productContext"; import RegularDropDown from "../../ui/inputs/RegularDropDown"; import RenameTooltip from "../../ui/features/RenameTooltip"; +import VersionSaved from "../sidebarRight/versionHisory/VersionSaved"; +import Footer from "../../footer/Footer"; +import ThreadChat from "../../ui/collaboration/ThreadChat"; +import Scene from "../../../modules/scene/scene"; +import useModuleStore, { useThreeDStore } from "../../../store/useModuleStore"; +import { usePlayButtonStore } from "../../../store/usePlayButtonStore"; +import { useSelectedZoneStore } from "../../../store/visualization/useZoneStore"; +import { useFloatingWidget } from "../../../store/visualization/useDroppedObjectsStore"; +import { useSelectedUserStore } from "../../../store/collaboration/useCollabStore"; +import { createHandleDrop } from "../../../modules/visualization/functions/handleUiDrop"; +import { useComparisonProduct, useMainProduct } from "../../../store/simulation/useSimulationStore"; +import { useProductContext } from "../../../modules/simulation/products/productContext"; import { setAssetsApi } from "../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; import { useParams } from "react-router-dom"; import { useSceneContext } from "../../../modules/scene/sceneContext"; import { useVersionHistoryStore } from "../../../store/builder/useVersionHistoryStore"; import { useVersionContext } from "../../../modules/builder/version/versionContext"; -import VersionSaved from "../sidebarRight/versionHisory/VersionSaved"; -import Footer from "../../footer/Footer"; -import ThreadChat from "../../ui/collaboration/ThreadChat"; import { useBuilderStore } from "../../../store/builder/useBuilderStore"; +import { recentlyViewed } from "../../../services/dashboard/recentlyViewed"; +import { getUserData } from "../../../functions/getUserData"; +import useRestStates from "../../../hooks/useResetStates"; function MainScene() { const { setMainProduct } = useMainProduct(); @@ -59,11 +55,19 @@ function MainScene() { const { products } = productStore(); const { setName, selectedAssets, setSelectedAssets } = assetStore(); const { projectId } = useParams() + const { organization, userId } = getUserData(); const { isRenameMode, setIsRenameMode } = useRenameModeStore(); const { versionHistory } = useVersionHistoryStore(); const { selectedVersionStore } = useVersionContext(); const { selectedVersion, setSelectedVersion } = selectedVersionStore(); const { selectedComment, commentPositionState } = useSelectedComment(); + const { resetStates } = useRestStates(); + + useEffect(() => { + return () => { + resetStates(); + } + }, []) useEffect(() => { if (activeModule !== 'simulation') { @@ -73,10 +77,20 @@ function MainScene() { }, [activeModule, clearComparisonProduct, setIsVersionSaved]) useEffect(() => { - if (versionHistory.length > 0) { - setSelectedVersion(versionHistory[0]) + if (versionHistory.length > 0 && organization && userId) { + recentlyViewed(organization, userId).then((projects) => { + const recent_opened_verisionID = (Object.values(projects?.RecentlyViewed || {})[0] as any)?.Present_version._id; + if (recent_opened_verisionID && projects.RecentlyViewed[0]._id === projectId) { + const version = versionHistory.find((ver) => ver.versionId === recent_opened_verisionID); + if (version) { + setSelectedVersion(version); + } + } else { + setSelectedVersion(versionHistory[0]); + } + }) } - }, [setSelectedVersion, versionHistory]) + }, [setSelectedVersion, versionHistory, projectId]) const handleSelectVersion = (option: string) => { const version = versionHistory.find((version) => version.versionName === option); diff --git a/app/src/components/layout/sidebarLeft/assetList/Assets.tsx b/app/src/components/layout/sidebarLeft/assetList/Assets.tsx index 5294be5..2f5a283 100644 --- a/app/src/components/layout/sidebarLeft/assetList/Assets.tsx +++ b/app/src/components/layout/sidebarLeft/assetList/Assets.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useMemo, useCallback } from "react"; -import { useDecalStore } from "../../../../store/builder/store"; import { getFilteredAssets } from "./assetsHelpers/filteredAssetsHelper"; import { fetchCategoryDecals } from "./assetsHelpers/fetchDecalsHelper"; +import { useBuilderStore } from "../../../../store/builder/useBuilderStore"; import { fetchAllAssets, fetchCategoryAssets, @@ -16,7 +16,7 @@ import { import { ArrowIcon } from "../../../icons/ExportCommonIcons"; const Assets: React.FC = () => { - const { selectedSubCategory, setSelectedSubCategory } = useDecalStore(); + const { selectedDecalCategory, setSelectedDecalCategory } = useBuilderStore(); const [searchValue, setSearchValue] = useState(null); const [selectedCategory, setSelectedCategory] = useState(null); const [assets, setAssets] = useState([]); @@ -31,9 +31,9 @@ const Assets: React.FC = () => { assets, searchValue, selectedCategory, - selectedSubCategory, + selectedDecalCategory, }), - [assets, searchValue, selectedCategory, selectedSubCategory] + [assets, searchValue, selectedCategory, selectedDecalCategory] ); const handleFetchCategory = useCallback( @@ -43,14 +43,14 @@ const Assets: React.FC = () => { if (category === "Decals") { const res = await fetchCategoryDecals("Safety"); setAssets(res); - setSelectedSubCategory("Safety"); + setSelectedDecalCategory("Safety"); } else { const res = await fetchCategoryAssets(category); setAssets(res); } setIsLoading(false); }, - [setSelectedSubCategory] + [setSelectedDecalCategory] ); const fetchGlobalSearch = useCallback(async (term: string) => { @@ -75,7 +75,7 @@ const Assets: React.FC = () => { return (
- +
{isLoading ? ( @@ -90,7 +90,7 @@ const Assets: React.FC = () => { className="back-button" onClick={() => { setSelectedCategory(null); - setSelectedSubCategory(null); + setSelectedDecalCategory(null); setAssets([]); setSearchValue(null); }} @@ -106,14 +106,13 @@ const Assets: React.FC = () => { {ACTIVE_DECAL_SUBCATEGORIES.map((cat) => (
{ setIsLoading(true); const res = await fetchCategoryDecals(cat.name); setAssets(res); - setSelectedSubCategory(cat.name); + setSelectedDecalCategory(cat.name); setIsLoading(false); }} > diff --git a/app/src/components/layout/sidebarLeft/assetList/assetsHelpers/filteredAssetsHelper.ts b/app/src/components/layout/sidebarLeft/assetList/assetsHelpers/filteredAssetsHelper.ts index 38d2b39..1ced2e2 100644 --- a/app/src/components/layout/sidebarLeft/assetList/assetsHelpers/filteredAssetsHelper.ts +++ b/app/src/components/layout/sidebarLeft/assetList/assetsHelpers/filteredAssetsHelper.ts @@ -2,19 +2,19 @@ interface FilterProps { assets: AssetProp[] | DecalProp[]; searchValue: string | null; selectedCategory: string | null; - selectedSubCategory: string | null; + selectedDecalCategory: string | null; } export const getFilteredAssets = ({ assets, searchValue, selectedCategory, - selectedSubCategory, + selectedDecalCategory, }: FilterProps) => { const term = searchValue?.trim().toLowerCase(); if (!term) return assets; - if (selectedCategory === "Decals" || selectedSubCategory) { + if (selectedCategory === "Decals" || selectedDecalCategory) { return (assets as DecalProp[]).filter((a) => a.decalName?.toLowerCase().includes(term) ); diff --git a/app/src/components/layout/sidebarLeft/assetList/assetsHelpers/renderAssetHelper.tsx b/app/src/components/layout/sidebarLeft/assetList/assetsHelpers/renderAssetHelper.tsx index bd553c1..9a3cf95 100644 --- a/app/src/components/layout/sidebarLeft/assetList/assetsHelpers/renderAssetHelper.tsx +++ b/app/src/components/layout/sidebarLeft/assetList/assetsHelpers/renderAssetHelper.tsx @@ -1,9 +1,10 @@ import React from "react"; -import { useDroppedDecal, useSelectedItem } from "../../../../../store/builder/store"; +import { useSelectedItem } from "../../../../../store/builder/store"; +import { useBuilderStore } from "../../../../../store/builder/useBuilderStore"; export const RenderAsset: React.FC<{ asset: AssetProp | DecalProp; index: number }> = ({ asset, index }) => { const { setSelectedItem } = useSelectedItem(); - const { setDroppedDecal } = useDroppedDecal(); + const { setDroppedDecal } = useBuilderStore(); if ("decalName" in asset) { return ( diff --git a/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx b/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx index e6493f4..e9c5baa 100644 --- a/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx +++ b/app/src/components/layout/sidebarRight/properties/AisleProperties.tsx @@ -282,7 +282,7 @@ const AisleProperties: React.FC = () => {