Refactor Dashboard components and styles for improved functionality and UI consistency

This commit is contained in:
Nalvazhuthi 2025-05-21 17:46:16 +05:30
parent 0ecb85a211
commit 8af4442a28
8 changed files with 520 additions and 328 deletions

View File

@ -1,10 +1,11 @@
import React from "react";
import React, { useState, useRef } from "react";
import { KebabIcon } from "../../icons/ExportCommonIcons";
import img from "../../../assets/image/image.png";
import { useNavigate } from "react-router-dom";
import { useProjectName } from "../../../store/builder/store";
import { viewProject } from "../../../services/dashboard/viewProject";
import { getUserData } from "./functions/getUserData";
import OuterClick from "../../../utils/outerClick";
interface DashBoardCardProps {
projectName: string;
@ -21,57 +22,112 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
projectId,
handleRestoreProject,
}) => {
let navigate = useNavigate();
const navigate = useNavigate();
const { setProjectName } = useProjectName();
const { userId, organization, userName } = getUserData();
const [isKebabOpen, setIsKebabOpen] = useState(false);
const handleKebabIconClick = async () => {
try {
if (handleRestoreProject) {
await handleRestoreProject(projectId);
} else if (handleDeleteProject) {
await handleDeleteProject(projectId);
}
} catch { }
};
const kebabRef = useRef<HTMLDivElement>(null);
const navigateToProject = async () => {
try {
const viewedProject = await viewProject(organization, projectId, userId);
console.log("Saved viewwdProject:", viewedProject);
console.log("Viewed project:", viewedProject);
} catch (error) {
console.error("Error deleting project:", error);
console.error("Error opening project:", error);
}
setProjectName(projectName);
navigate(`/${projectId}`);
};
const handleOptionClick = async (option: string) => {
switch (option) {
case "delete":
if (handleDeleteProject) {
await handleDeleteProject(projectId);
}
break;
case "restore":
if (handleRestoreProject) {
await handleRestoreProject(projectId);
}
break;
case "openInNewTab":
window.open(`/${projectId}`, "_blank");
break;
case "rename":
// Add rename logic here
break;
case "duplicate":
// Add duplication logic here
break;
default:
break;
}
setIsKebabOpen(false);
};
OuterClick({
contextClassName: ["kebab-wrapper", "kebab-options-wrapper"],
setMenuVisible: () => setIsKebabOpen(false),
});
return (
<div className="dashboard-card-container" onClick={navigateToProject} title={projectName}>
<button
className="dashboard-card-container"
onClick={navigateToProject}
title={projectName}
>
<div className="dashboard-card-wrapper">
<div className="preview-container">
{thumbnail ? <img src={thumbnail} alt="" /> : <img src={img} alt="" />}
{thumbnail ? (
<img src={thumbnail} alt="" />
) : (
<img src={img} alt="" />
)}
</div>
<div className="project-details-container">
<div className="project-details">
<div className="project-name">{projectName}</div>
<div className="project-data">24-12-2025</div>
</div>
<div className="users-list-container">
<div className="users-list-container" ref={kebabRef}>
<div className="user-profile">
{userName ? userName.charAt(0).toUpperCase() : "Anonymous"}
{userName ? userName.charAt(0).toUpperCase() : "A"}
</div>
<div
<button
className="kebab-wrapper"
onClick={(e) => {
e.stopPropagation();
handleKebabIconClick();
e.stopPropagation(); // Prevents click from bubbling up
console.log("Kebab menu clicked");
setIsKebabOpen((prev) => !prev);
}}
>
<KebabIcon />
</button>
</div>
</div>
</div>
{isKebabOpen && (
<div className="kebab-options-wrapper">
{["rename", "delete", "duplicate", "open in new tab"].map((option) => (
<button
key={option}
className="option"
onClick={(e) => {
console.log(option);
e.stopPropagation();
handleOptionClick(option);
}}
>
{option}
</button>
))}
</div>
)}
</button>
);
};

View File

@ -43,7 +43,11 @@ const DashboardHome: React.FC = () => {
setIsSearchActive(false);
return;
}
const filterRecentProcess = await searchProject(organization, userId, inputValue);
const filterRecentProcess = await searchProject(
organization,
userId,
inputValue
);
setIsSearchActive(true);
setRecentProjects(filterRecentProcess.message ? {} : filterRecentProcess);
};
@ -127,7 +131,7 @@ const DashboardHome: React.FC = () => {
<MarketPlaceBanner />
<div className="container">
<h2 className="section-header">Recent Projects</h2>
<h2 className="section-header">Recents</h2>
<div className="cards-container">{renderProjects()}</div>
</div>
</div>
@ -135,3 +139,5 @@ const DashboardHome: React.FC = () => {
};
export default DashboardHome;

View File

@ -1,11 +1,11 @@
import React, { useEffect, useState } from 'react';
import DashboardNavBar from './DashboardNavBar';
import DashboardCard from './DashboardCard';
import { getAllProjects } from '../../../services/dashboard/getAllProjects';
import { getUserData } from './functions/getUserData';
import { searchProject } from '../../../services/dashboard/searchProjects';
import { deleteProject } from '../../../services/dashboard/deleteProject';
import { useSocketStore } from '../../../store/builder/store';
import React, { useEffect, useState } from "react";
import DashboardNavBar from "./DashboardNavBar";
import DashboardCard from "./DashboardCard";
import { getAllProjects } from "../../../services/dashboard/getAllProjects";
import { getUserData } from "./functions/getUserData";
import { searchProject } from "../../../services/dashboard/searchProjects";
import { deleteProject } from "../../../services/dashboard/deleteProject";
import { useSocketStore } from "../../../store/builder/store";
interface Project {
_id: string;
@ -20,7 +20,9 @@ interface WorkspaceProjects {
}
const DashboardProjects: React.FC = () => {
const [workspaceProjects, setWorkspaceProjects] = useState<WorkspaceProjects>({});
const [workspaceProjects, setWorkspaceProjects] = useState<WorkspaceProjects>(
{}
);
const [isSearchActive, setIsSearchActive] = useState<boolean>(false);
const [activeFolder, setActiveFolder] = useState<string>("myProjects");
const { dashBoardSocket } = useSocketStore();
@ -37,23 +39,24 @@ const DashboardProjects: React.FC = () => {
console.error("Error fetching projects:", error);
}
};
const handleProjectsSearch = async (
inputValue: string
) => {
const handleProjectsSearch = async (inputValue: string) => {
if (!inputValue.trim()) {
setIsSearchActive(false);
return;
}
if (!setWorkspaceProjects || !setIsSearchActive) return;
const searchedProject = await searchProject(organization, userId, inputValue);
const searchedProject = await searchProject(
organization,
userId,
inputValue
);
setIsSearchActive(true);
setWorkspaceProjects(searchedProject.message ? {} : searchedProject);
};
const handleDeleteProject = async (projectId: any) => {
try {
const Organization = organization
const Organization = organization;
// const deletedProject = await deleteProject(
// projectId,
// userId,
@ -62,11 +65,11 @@ const DashboardProjects: React.FC = () => {
const deleteProject = {
projectId,
organization: organization,
userId
}
userId,
};
if (dashBoardSocket) {
const handleResponse = (data: any) => {
console.log('Project add response:', data);
console.log("Project add response:", data);
dashBoardSocket.off("v1-project:response:delete", handleResponse); // Clean up
};
@ -85,17 +88,15 @@ const DashboardProjects: React.FC = () => {
);
return {
...prevDiscardedProjects,
Projects: updatedProjectDatas
Projects: updatedProjectDatas,
};
});
setIsSearchActive(false)
setIsSearchActive(false);
} catch (error) {
console.error('Error deleting project:', error);
console.error("Error deleting project:", error);
}
};
const renderProjects = () => {
if (activeFolder !== "myProjects") return null;
@ -129,24 +130,22 @@ const DashboardProjects: React.FC = () => {
handleProjectsSearch={handleProjectsSearch}
/>
<div className="container">
<div className="header" style={{ display: "flex", gap: "7px" }}>
<div
style={{ color: activeFolder === "myProjects" ? "#c4abf1" : "black" }}
<div className="container" style={{ height: "calc(100% - 87px)" }}>
<div className="header-wrapper" style={{ display: "flex", gap: "7px" }}>
<button
className={`header ${activeFolder === "myProjects" && "active"}`}
onClick={() => setActiveFolder("myProjects")}
>
My Projects
</div>
<div
style={{ color: activeFolder === "shared" ? "#c4abf1" : "black" }}
</button>
<button
className={`header ${activeFolder === "shared" && "active"}`}
onClick={() => setActiveFolder("shared")}
>
Shared with me
</button>
</div>
</div>
<div className="cards-container">
{renderProjects()}
</div>
<div className="cards-container">{renderProjects()}</div>
</div>
</div>
);

View File

@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react';
import { getTrash } from '../../../services/dashboard/getTrash';
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 React, { useEffect, useState } from "react";
import { getTrash } from "../../../services/dashboard/getTrash";
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";
interface Project {
_id: string;
@ -19,49 +19,52 @@ interface DiscardedProjects {
}
const DashboardTrash: React.FC = () => {
const [discardedProjects, setDiscardedProjects] = useState<DiscardedProjects>({});
console.log('discardedProjects: ', discardedProjects);
const [discardedProjects, setDiscardedProjects] = useState<DiscardedProjects>(
{}
);
console.log("discardedProjects: ", discardedProjects);
const [isSearchActive, setIsSearchActive] = useState(false);
const { userId, organization } = getUserData();
const fetchTrashProjects = async () => {
try {
const projects = await getTrash(organization);
console.log('organization: ', organization);
console.log('trashedprojects: ', projects);
console.log("organization: ", organization);
console.log("trashedprojects: ", projects);
if (JSON.stringify(projects) !== JSON.stringify(discardedProjects)) {
setDiscardedProjects(projects);
}
} catch (error) {
console.error('Error fetching trash projects:', error);
console.error("Error fetching trash projects:", error);
}
};
const handleTrashSearch = async (
inputValue: string
) => {
const handleTrashSearch = async (inputValue: string) => {
if (!inputValue.trim()) {
setIsSearchActive(false);
return;
}
if (!setDiscardedProjects || !setIsSearchActive) return;
const filterTrashedProcess = await trashSearchProject(organization, userId, inputValue);
const filterTrashedProcess = await trashSearchProject(
organization,
userId,
inputValue
);
setIsSearchActive(true);
setDiscardedProjects(filterTrashedProcess.message ? {} : filterTrashedProcess);
setDiscardedProjects(
filterTrashedProcess.message ? {} : filterTrashedProcess
);
};
const handleRestoreProject = async (projectId: any) => {
try {
const Organization = organization;
const restoreProject = await restoreTrash(
Organization,
projectId
);
const restoreProject = await restoreTrash(Organization, projectId);
setDiscardedProjects((prevDiscardedProjects: DiscardedProjects) => {
// Check if TrashDatas exists and is an array
if (!Array.isArray(prevDiscardedProjects?.TrashDatas)) {
console.error('TrashDatas is not an array', prevDiscardedProjects);
console.error("TrashDatas is not an array", prevDiscardedProjects);
return prevDiscardedProjects;
}
const updatedTrashDatas = prevDiscardedProjects.TrashDatas.filter(
@ -69,12 +72,12 @@ const DashboardTrash: React.FC = () => {
);
return {
...prevDiscardedProjects,
TrashDatas: updatedTrashDatas
TrashDatas: updatedTrashDatas,
};
});
setIsSearchActive(false)
setIsSearchActive(false);
} catch (error) {
console.error('Error deleting project:', error);
console.error("Error deleting project:", error);
}
};
const renderTrashProjects = () => {
@ -96,7 +99,7 @@ const DashboardTrash: React.FC = () => {
};
useEffect(() => {
console.log('isSearchActive:trash ', isSearchActive);
console.log("isSearchActive:trash ", isSearchActive);
if (!isSearchActive) {
fetchTrashProjects();
}
@ -104,16 +107,11 @@ const DashboardTrash: React.FC = () => {
return (
<div className="dashboard-home-container">
<DashboardNavBar
page="trash"
handleTrashSearch={handleTrashSearch}
/>
<DashboardNavBar page="trash" handleTrashSearch={handleTrashSearch} />
<div className="container">
<div className="header" style={{ display: 'flex', gap: '7px' }}></div>
<div className="cards-container">
{renderTrashProjects()}
</div>
<div className="container" style={{ height: "calc(100% - 87px)" }}>
<div className="header" style={{ display: "flex", gap: "7px" }}></div>
<div className="cards-container">{renderTrashProjects()}</div>
</div>
</div>
);

View File

@ -49,7 +49,7 @@ const DashboardTutorial = () => {
page="tutorial"
/>
<div className="container">
<div className="container" style={{ height: "calc(100% - 87px)" }}>
<div className="header" style={{ display: 'flex', gap: '7px' }}></div>
<div className="cards-container">
{renderTrashProjects()}

View File

@ -1,15 +1,18 @@
import React, { useEffect, useState } from 'react';
import SidePannel from '../components/layout/Dashboard/SidePannel';
import DashboardHome from '../components/layout/Dashboard/DashboardHome';
import DashboardProjects from '../components/layout/Dashboard/DashboardProjects';
import DashboardTrash from '../components/layout/Dashboard/DashboardTrash';
import { useOrganization, useSocketStore, useUserName, useZones } from '../store/builder/store';
import { getUserData } from '../components/layout/Dashboard/functions/getUserData';
import DashboardTutorial from '../components/layout/Dashboard/DashboardTutorial';
import React, { useEffect, useState } from "react";
import SidePannel from "../components/layout/Dashboard/SidePannel";
import DashboardHome from "../components/layout/Dashboard/DashboardHome";
import DashboardProjects from "../components/layout/Dashboard/DashboardProjects";
import DashboardTrash from "../components/layout/Dashboard/DashboardTrash";
import {
useOrganization,
useSocketStore,
useUserName,
} from "../store/builder/store";
import { getUserData } from "../components/layout/Dashboard/functions/getUserData";
import DashboardTutorial from "../components/layout/Dashboard/DashboardTutorial";
const Dashboard: React.FC = () => {
const [activeTab, setActiveTab] = useState<string>('Home');
const [activeTab, setActiveTab] = useState<string>("Home");
const { setUserName } = useUserName();
const { setOrganization } = useOrganization();
const { userId, organization, email, userName } = getUserData();
@ -21,14 +24,14 @@ const Dashboard: React.FC = () => {
setUserName(userName);
}
}
}, [])
}, []);
return (
<div className="dashboard-main">
<SidePannel setActiveTab={setActiveTab} activeTab={activeTab} />
{activeTab == 'Home' && <DashboardHome />}
{activeTab == 'Projects' && <DashboardProjects />}
{activeTab == 'Trash' && <DashboardTrash />}
{activeTab == 'Tutorials' && <DashboardTutorial />}
{activeTab == "Home" && <DashboardHome />}
{activeTab == "Projects" && <DashboardProjects />}
{activeTab == "Trash" && <DashboardTrash />}
{activeTab == "Tutorials" && <DashboardTutorial />}
</div>
);
};

View File

@ -5,19 +5,28 @@
height: 100vh;
width: 100vw;
display: flex;
padding: 27px 17px;
.side-pannel-container {
padding: 32px;
min-width: 240px;
height: 100vh;
min-width: 280px;
height: 100%;
display: flex;
flex-direction: column;
gap: 16px;
border-right: 1px solid var(--border-color);
// border-right: 1px solid var(--border-color);
background: var(--background-color);
backdrop-filter: blur(20px);
border-radius: 30px;
box-shadow: var(--box-shadow-medium);
.side-pannel-header {
@include flex-space-between;
.user-container {
@include flex-center;
gap: 6px;
.user-profile {
height: 32px;
width: 32px;
@ -28,10 +37,12 @@
color: var(--primary-color);
border-radius: #{$border-radius-circle};
}
.user-name {
color: var(--accent-color);
}
}
.notifications-container {
@include flex-center;
height: 24px;
@ -39,82 +50,117 @@
cursor: pointer;
}
}
.new-project-button {
padding: 12px 16px;
cursor: not-allowed;
color: var(--accent-color);
color: var(--text-color);
background: var(--background-color-secondary);
border-radius: #{$border-radius-large};
border-radius: #{$border-radius-xxx};
}
.side-bar-content-container {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
.side-bar-options-container {
.option-list {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
padding: 8px 10px;
margin: 4px 0;
border-radius: #{$border-radius-medium};
border-radius: #{$border-radius-extra-large};
cursor: pointer;
&:hover {
background: var(--background-color-secondary);
}
}
.active {
color: var(--accent-color);
color: var(--text-button-color);
font-weight: var(--font-weight-medium);
background: var(--highlight-accent-color);
background: var(--background-color-button);
&:hover {
background: var(--highlight-accent-color);
}
background: var(--background-color-button);
}
}
}
}
}
.dashboard-home-container {
width: 100%;
padding-left: 18px;
.dashboard-navbar-container {
margin-top: 28px;
padding: 8px 34px 8px 12px;
margin-bottom: 22px;
@include flex-center;
.title {
text-transform: capitalize;
font-size: var(--font-size-large);
width: 100%;
}
.market-place-button {
@include flex-center;
gap: 6px;
padding: 8px 14px;
background: var(--accent-gradient-color);
background: var(--background-color-button);
white-space: nowrap;
border-radius: #{$border-radius-large};
color: var(--primary-color);
border-radius: #{$border-radius-extra-large};
color: var(--text-button-color);
}
.search-wrapper {
width: 400px;
}
}
.container {
margin: 22px 0;
width: 100%;
padding: 0 12px;
.header {
height: calc(100% - 357px);
.header-wrapper {
font-size: var(--font-size-large);
.header {
color: var(--input-text-color);
padding: 6px 8px;
border-radius: #{$border-radius-extra-large};
&.active {
background: var(--background-color-button);
color: var(--text-color);
}
}
}
.cards-container {
height: 100%;
display: flex;
flex-wrap: wrap;
position: relative;
width: 100%;
padding: 8px;
padding-top: 18px;
gap: 18px;
overflow: auto;
}
}
}
}
.dashboard-card-container {
@ -123,11 +169,23 @@
min-width: 260px;
position: relative;
border: 1px solid var(--border-color);
border-radius: #{$border-radius-large};
border-radius: #{$border-radius-extra-large};
overflow: hidden;
cursor: pointer;
overflow: visible;
position: relative;
.dashboard-card-wrapper {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
}
.preview-container {
height: 100%;
width: 100%;
img {
height: 100%;
width: 100%;
@ -135,27 +193,38 @@
vertical-align: top;
border: none;
outline: none;
border-radius: #{$border-radius-extra-large};
}
}
.project-details-container {
@include flex-space-between;
position: absolute;
bottom: 0;
width: 100%;
padding: 8px 16px;
background: var(--primary-color);
border-radius: #{$border-radius-large};
padding: 13px 16px;
background: var(--background-color);
// backdrop-filter: blur(18px);
border-radius: #{$border-radius-xlarge};
transform: translateY(100%);
transition: transform 0.25s linear;
.project-details {
.project-name {
margin-bottom: 2px;
margin-bottom: 7px;
}
.project-data {
color: var(--accent-color);
color: var(--input-text-color);
}
}
.users-list-container {
@include flex-center;
gap: 6px;
position: relative; // Needed for absolute positioning of kebab-options-wrapper
.user-profile {
height: 26px;
width: 26px;
@ -165,8 +234,65 @@
color: var(--primary-color);
border-radius: #{$border-radius-circle};
}
.kebab {
padding: 10px;
@include flex-center;
transform: rotate(90deg);
}
}
}
.kebab-options-wrapper {
position: absolute;
bottom: 40px;
right: 40px;
background: var(--background-color);
border: 1px solid var(--border-color);
border-radius: 8px;
z-index: 100;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
transform: translate(100%, 100%);
overflow: hidden;
display: none;
.option {
padding: 8px 12px;
font-size: 14px;
text-align: left;
background: transparent;
border: none;
color: var(--text-color);
cursor: pointer;
transition: background 0.2s ease;
text-transform: capitalize;
&:hover {
background-color: var(--background-color-secondary);
}
}
}
&:hover {
overflow: visible;
.kebab-options-wrapper {
display: flex;
}
.project-details-container {
transform: translateY(0);
}
}
}
.market-place-banner-container {
@ -174,13 +300,14 @@
height: 230px;
overflow: hidden;
position: relative;
padding: 0 24px;
img {
height: 100%;
width: 100%;
object-fit: cover;
border-radius: #{$border-radius-xxx};
}
.hero-text {
position: absolute;
left: 52px;
@ -191,6 +318,7 @@
color: #ffffff;
text-transform: uppercase;
}
.context {
position: absolute;
top: 20px;
@ -201,11 +329,13 @@
color: #ffffff;
font-family: #{$font-roboto};
}
.arrow-context {
position: absolute;
bottom: 27px;
right: 300px;
}
.explore-button {
position: absolute;
top: 95px;