diff --git a/app/src/components/Dashboard/DashboardCard.tsx b/app/src/components/Dashboard/DashboardCard.tsx index c15765e..5a6a00f 100644 --- a/app/src/components/Dashboard/DashboardCard.tsx +++ b/app/src/components/Dashboard/DashboardCard.tsx @@ -3,11 +3,7 @@ import { createPortal } from "react-dom"; import { useNavigate } from "react-router-dom"; import img from "../../assets/image/image.png"; import { getUserData } from "../../functions/getUserData"; -import { - useLoadingProgress, - useProjectName, - useSocketStore, -} from "../../store/builder/store"; +import { useLoadingProgress, useProjectName, useSocketStore } from "../../store/builder/store"; import OuterClick from "../../utils/outerClick"; import { KebabIcon } from "../icons/ExportCommonIcons"; import { getAllProjects } from "../../services/dashboard/getAllProjects"; @@ -15,323 +11,275 @@ import { getAllProjects } from "../../services/dashboard/getAllProjects"; // import { updateProject } from "../../services/dashboard/updateProject"; interface DashBoardCardProps { - projectName: string; - thumbnail: string; - projectId: string; - createdAt?: string; - isViewed?: string; - createdBy?: { _id: string; userName: string }; - handleDeleteProject?: (projectId: string) => Promise; - handleTrashDeleteProject?: (projectId: string) => Promise; - handleRestoreProject?: (projectId: string) => Promise; - handleDuplicateWorkspaceProject?: ( - projectId: string, - projectName: string, - thumbnail: string, - userId?: string - ) => Promise; - handleDuplicateRecentProject?: ( - projectId: string, - projectName: string, - thumbnail: string - ) => Promise; - active?: "shared" | "trash" | "recent" | string; - setIsSearchActive?: React.Dispatch>; - setRecentDuplicateData?: React.Dispatch>; - setProjectDuplicateData?: React.Dispatch>; - setActiveFolder?: React.Dispatch>; - openKebabProjectId: string | null; - setOpenKebabProjectId: React.Dispatch>; + projectName: string; + thumbnail: string; + projectId: string; + createdAt?: string; + isViewed?: string; + createdBy?: { _id: string; userName: string }; + handleDeleteProject?: (projectId: string) => Promise; + handleTrashDeleteProject?: (projectId: string) => Promise; + handleRestoreProject?: (projectId: string) => Promise; + handleDuplicateWorkspaceProject?: (projectId: string, projectName: string, thumbnail: string, userId?: string) => Promise; + handleDuplicateRecentProject?: (projectId: string, projectName: string, thumbnail: string) => Promise; + active?: "shared" | "trash" | "recent" | string; + setIsSearchActive?: React.Dispatch>; + setRecentDuplicateData?: React.Dispatch>; + setProjectDuplicateData?: React.Dispatch>; + setActiveFolder?: React.Dispatch>; + openKebabProjectId: string | null; + setOpenKebabProjectId: React.Dispatch>; } -type RelativeTimeFormatUnit = - | "year" - | "month" - | "week" - | "day" - | "hour" - | "minute" - | "second"; +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"], + default: ["rename", "delete", "duplicate", "open in new tab"], + trash: ["restore", "delete"], + shared: ["duplicate", "open in new tab"], }; const DashboardCard: React.FC = ({ - projectName, - thumbnail, - projectId, - active, - handleDeleteProject, - handleRestoreProject, - handleTrashDeleteProject, - handleDuplicateWorkspaceProject, - handleDuplicateRecentProject, - createdAt, - createdBy, - setRecentDuplicateData, - setProjectDuplicateData, - setActiveFolder, - openKebabProjectId, - setOpenKebabProjectId, -}) => { - const navigate = useNavigate(); - const { setProjectName } = useProjectName(); - const { userId, organization, userName } = getUserData(); - const { projectSocket } = useSocketStore(); - const { setLoadingProgress } = useLoadingProgress(); - - const isKebabOpen = openKebabProjectId === projectId; - const [renameValue, setRenameValue] = useState(projectName); - const [isRenaming, setIsRenaming] = useState(false); - const kebabRef = useRef(null); - - // Close kebab when clicking outside - OuterClick({ - contextClassName: [`tag-${projectId}`], - setMenuVisible: () => { - if (isKebabOpen) setOpenKebabProjectId(null); - }, - }); - - const navigateToProject = useCallback(() => { - if (active === "trash") return; - setLoadingProgress(1); - setProjectName(projectName); - navigate(`/projects/${projectId}`); - }, [ - active, - projectId, projectName, - navigate, - setLoadingProgress, - setProjectName, - ]); + thumbnail, + projectId, + active, + handleDeleteProject, + handleRestoreProject, + handleTrashDeleteProject, + handleDuplicateWorkspaceProject, + handleDuplicateRecentProject, + createdAt, + createdBy, + setRecentDuplicateData, + setProjectDuplicateData, + setActiveFolder, + openKebabProjectId, + setOpenKebabProjectId, +}) => { + const navigate = useNavigate(); + const { setProjectName } = useProjectName(); + const { userId, organization, userName } = getUserData(); + const { projectSocket } = useSocketStore(); + const { setLoadingProgress } = useLoadingProgress(); - 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 isKebabOpen = openKebabProjectId === projectId; + const [renameValue, setRenameValue] = useState(projectName); + const [isRenaming, setIsRenaming] = useState(false); + const kebabRef = useRef(null); - const handleProjectName = useCallback( - async (newName: string) => { - setRenameValue(newName); - if (!projectId) return; + // Close kebab when clicking outside + OuterClick({ + contextClassName: [`tag-${projectId}`], + setMenuVisible: () => { + if (isKebabOpen) setOpenKebabProjectId(null); + }, + }); - try { - const projects = await getAllProjects(userId, organization); - const projectUuid = projects?.Projects?.find( - (val: any) => val.projectUuid === projectId || val._id === projectId - ); - if (!projectUuid) return; + const navigateToProject = useCallback(() => { + if (active === "trash") return; + setLoadingProgress(1); + setProjectName(projectName); + navigate(`/projects/${projectId}`); + }, [active, projectId, projectName, navigate, setLoadingProgress, setProjectName]); - const updatePayload = { - projectId: projectUuid._id, - organization, - userId, - projectName: newName, + 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); + } + } catch { + // silent fail + } + }, + [projectId, userId, organization, projectSocket] + ); + + const handleOptionClick = useCallback( + async (option: string) => { + switch (option) { + case "delete": + await (active === "trash" ? handleTrashDeleteProject?.(projectId) : handleDeleteProject?.(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, userId); + if (active === "shared") { + setActiveFolder?.("myProjects"); + } + } else if (handleDuplicateRecentProject) { + setRecentDuplicateData?.({ + projectId, + projectName, + thumbnail, + userId, + }); + await handleDuplicateRecentProject(projectId, projectName, thumbnail); + } + break; + } + }, + [ + projectId, + projectName, + thumbnail, + userId, + active, + handleDeleteProject, + handleTrashDeleteProject, + handleRestoreProject, + handleDuplicateWorkspaceProject, + handleDuplicateRecentProject, + setProjectName, + setProjectDuplicateData, + setRecentDuplicateData, + setActiveFolder, + ] + ); + + const getRelativeTime = useCallback((dateString: string): string => { + const date = new Date(dateString); + const now = new Date(); + const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); + + const intervals: Record = { + year: 31536000, + month: 2592000, + week: 604800, + day: 86400, + hour: 3600, + minute: 60, + second: 1, }; - if (projectSocket) { - projectSocket.emit("v1:project:update", updatePayload); + const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); + for (const [unit, seconds] of Object.entries(intervals)) { + const diff = Math.floor(diffInSeconds / seconds); + if (diff >= 1) return rtf.format(-diff, unit as RelativeTimeFormatUnit); } - } catch { - // silent fail - } - }, - [projectId, userId, organization, projectSocket] - ); + return "just now"; + }, []); - 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, - userId - ); - if (active === "shared") { - setActiveFolder?.("myProjects"); - } - } else if (handleDuplicateRecentProject) { - setRecentDuplicateData?.({ - projectId, - projectName, - thumbnail, - userId, + const [kebabPosition, setKebabPosition] = useState({ top: 0, left: 0 }); + + useEffect(() => { + if (isKebabOpen && kebabRef.current) { + const rect = kebabRef.current.getBoundingClientRect(); + setKebabPosition({ + top: rect.bottom + window.scrollY, + left: rect.left + window.scrollX - 80, }); - await handleDuplicateRecentProject( - projectId, - projectName, - thumbnail - ); - } - break; - } - }, - [ - projectId, - projectName, - thumbnail, - userId, - active, - handleDeleteProject, - handleTrashDeleteProject, - handleRestoreProject, - handleDuplicateWorkspaceProject, - handleDuplicateRecentProject, - setProjectName, - setProjectDuplicateData, - setRecentDuplicateData, - setActiveFolder, - ] - ); + } + }, [isKebabOpen]); - const getRelativeTime = useCallback((dateString: string): string => { - const date = new Date(dateString); - const now = new Date(); - const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); + return ( +
+
+
+ {`${projectName} +
- const intervals: Record = { - year: 31536000, - month: 2592000, - week: 604800, - day: 86400, - hour: 3600, - minute: 60, - second: 1, - }; +
e.stopPropagation()}> +
+ {isRenaming ? ( + handleProjectName(e.target.value)} + onBlur={() => { + setIsRenaming(false); + setProjectName(renameValue); + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + setIsRenaming(false); + setProjectName(renameValue); + } + }} + aria-label="Rename project" + autoFocus + /> + ) : ( + {renameValue} + )} + {createdAt && ( +
+ {active === "trash" ? "Trashed" : "Edited"} {getRelativeTime(createdAt)} +
+ )} +
- const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" }); - 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 [kebabPosition, setKebabPosition] = useState({ top: 0, left: 0 }); - - useEffect(() => { - if (isKebabOpen && kebabRef.current) { - const rect = kebabRef.current.getBoundingClientRect(); - setKebabPosition({ - top: rect.bottom + window.scrollY, - left: rect.left + window.scrollX - 80, - }); - } - }, [isKebabOpen]); - - return ( -
-
-
- {`${projectName} -
- -
e.stopPropagation()} - > -
- {isRenaming ? ( - handleProjectName(e.target.value)} - onBlur={() => { - setIsRenaming(false); - setProjectName(renameValue); - }} - onKeyDown={(e) => { - if (e.key === "Enter") { - setIsRenaming(false); - setProjectName(renameValue); - } - }} - aria-label="Rename project" - autoFocus - /> - ) : ( - {renameValue} - )} - {createdAt && ( -
- {active === "trash" ? "Trashed" : "Edited"}{" "} - {getRelativeTime(createdAt)} -
- )} -
- -
-
- {(createdBy?.userName || userName || "A").charAt(0).toUpperCase()} +
+
{(createdBy?.userName || userName || "A").charAt(0).toUpperCase()}
+ +
+
- -
-
-
- {isKebabOpen && - createPortal( -
- {getOptions().map((option) => ( - - ))} -
, - document.body - )} -
- ); + {isKebabOpen && + createPortal( +
+ {getOptions().map((option) => ( + + ))} +
, + document.body + )} +
+ ); }; export default DashboardCard;