import React, { useState, useRef, useCallback, useEffect } from "react"; 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 OuterClick from "../../utils/outerClick"; import { KebabIcon } from "../icons/ExportCommonIcons"; import { getAllProjectsApi } from "../../services/dashboard/getAllProjectsApi"; // import { viewProject } from "../../services/dashboard/viewProject"; // 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>; } 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, 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]); 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 getAllProjectsApi(); 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, }; 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()}
{isKebabOpen && createPortal(
{getOptions().map((option) => ( ))}
, document.body )}
); }; export default DashboardCard;