Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Internal/adapters/repository/projectRepo.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,11 @@ func (pr *ProjectRepo) FetchProjects(projects []domain.Project) ([]domain.Projec
}
return projects, nil
}

func (pr *ProjectRepo) ProjectById(id string) (*domain.Project, error) {
var project domain.Project
if err := pr.DB.Gorm.First(&project, id).Error; err != nil {
return nil, err
}
return &project, nil
}
1 change: 1 addition & 0 deletions Internal/api/Routes/routev1.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func SetupRouterV1(r *gin.Engine, deps Deps) {
project := protected.Group("/project")
{
project.GET("/projects", deps.Project.GetAllProjectHandler)
project.GET("/:projectid", deps.Project.GetProjectHandler)
}
}
}
Expand Down
16 changes: 16 additions & 0 deletions Internal/api/handlers/projectHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,19 @@ func (ph *ProjectHandler) GetAllProjectHandler(c *gin.Context) {
"data": projects,
})
}

func (ph *ProjectHandler) GetProjectHandler(c *gin.Context) {
projectId := c.Param("projectid")
project, err := ph.ProjectService.GetProjectById(projectId)
if err != nil {
c.JSON(500, gin.H{
"message": "failed to get project",
"data": err.Error(),
})
return
}
c.JSON(200, gin.H{
"message": "success in fetching project",
"data": project,
})
}
1 change: 1 addition & 0 deletions Internal/api/middleware/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func AuthMiddleware(userRepo *repository.UserRepository, cfg *config.Config) gin
"message": "Authorization header not found",
"data": err.Error(),
})
c.Abort()
return
}
parts := strings.Split(authHeader, " ")
Expand Down
8 changes: 8 additions & 0 deletions Internal/core/services/projectService.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,11 @@ func (ps *ProjectService) GetProjects(Role string) ([]domain.Project, error) {
}
return project, nil
}

func (ps *ProjectService) GetProjectById(id string) (*domain.Project, error) {
project, err := ps.ProjectRepo.ProjectById(id)
if err != nil {
return nil, err
}
return project, nil
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Textarea } from "../../../components/ui/textarea";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { useAuth } from "@/features/Login/context/AuthContext";
Expand Down Expand Up @@ -43,7 +43,7 @@
// Reset form
setName("");
setDesc("");
} catch (err: any) {

Check failure on line 46 in Lb-web/src/features/project/components/CreateProjectModal.tsx

View workflow job for this annotation

GitHub Actions / Frontend integrity checks

Unexpected any. Specify a different type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
setError((err as any).response?.data?.message || "Failed to create project");
} finally {
Expand Down
113 changes: 113 additions & 0 deletions Lb-web/src/features/project/pages/ProjectDashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { useEffect, useState } from "react";
import { useParams, Link } from "react-router";
import { projectService } from "../services/projectService";
import { Button } from "@/components/ui/button";
import { ArrowLeft, LayoutDashboard, Settings } from "lucide-react";


export default function ProjectDashboard() {
const { id } = useParams<{ id: string }>();
const [project, setProject] = useState<{ id: number; name: string; desc: string; created_at: string } | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
const fetchProject = async () => {
if (!id) return;
setIsLoading(true);
try {
const data = await projectService.getProjectById(id);
setProject(data);
} catch (err: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
setError("Failed to load project details.");
console.error(err);
} finally {
setIsLoading(false);
}
};

fetchProject();
}, [id]);

if (isLoading) {
return (
<div className="flex items-center justify-center h-screen bg-slate-50">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-sky-600"></div>
</div>
);
}

if (error || !project) {
return (
<div className="flex flex-col items-center justify-center h-screen bg-slate-50">
<h2 className="text-2xl font-bold text-sky-900 mb-4">{error || "Project not found"}</h2>
<Link to="/">
<Button variant="outline">
<ArrowLeft className="mr-2 h-4 w-4" />
Back to Home
</Button>
</Link>
</div>
);
}

return (
<div className="flex flex-col h-screen bg-slate-50 relative overflow-hidden">
{/* Subtle Background */}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#e0f2fe_1px,transparent_1px),linear-gradient(to_bottom,#e0f2fe_1px,transparent_1px)] bg-size-[24px_24px] mask-[radial-gradient(ellipse_60%_50%_at_50%_0%,#000_70%,transparent_100%)] opacity-90 pointer-events-none"></div>

{/* Sidebar (Mockup for now, or just a simple header) */}
{/* Let's stick to a clean layout similar to Home but focused on the project */}

<header className="flex items-center justify-between px-6 py-4 bg-white/50 backdrop-blur-md border-b border-sky-100 z-20">
<div className="flex items-center space-x-4">
<Link to="/">
<Button variant="ghost" size="icon" className="text-sky-700 hover:text-sky-900 hover:bg-sky-100">
<ArrowLeft className="h-5 w-5" />
</Button>
</Link>
<div>
<h1 className="text-xl font-bold text-sky-900 tracking-tight">{project.name}</h1>
<p className="text-xs text-sky-500 font-medium">Project Dashboard</p>
</div>
</div>
<div className="flex items-center space-x-2">
<Button variant="ghost" size="icon" className="text-sky-700 hover:text-sky-900 hover:bg-sky-100">
<LayoutDashboard className="h-5 w-5" />
</Button>
<Button variant="ghost" size="icon" className="text-sky-700 hover:text-sky-900 hover:bg-sky-100">
<Settings className="h-5 w-5" />
</Button>
</div>
</header>

<main className="flex-1 p-8 z-10 overflow-y-auto">
<div className="max-w-5xl mx-auto">
<div className="bg-white/60 backdrop-blur-sm p-8 rounded-2xl shadow-sm border border-sky-100 mb-8">
<h2 className="text-2xl font-semibold text-sky-900 mb-4">Welcome to {project.name}</h2>
<p className="text-sky-700/80 leading-relaxed mb-6">
{project.desc || "No description provided for this project."}
</p>
<div className="flex items-center space-x-4 text-sm text-sky-500">
<span>Created on {new Date(project.created_at).toLocaleDateString()}</span>
{/* Potential place for more stats like '3 Members', '5 Tasks', etc. */}
</div>
</div>

{/* Placeholder for future dashboard widgets */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div className="bg-white/40 backdrop-blur-sm p-6 rounded-xl border border-sky-100 h-48 flex items-center justify-center text-sky-300">
Widget Placeholder
</div>
<div className="bg-white/40 backdrop-blur-sm p-6 rounded-xl border border-sky-100 h-48 flex items-center justify-center text-sky-300">
Widget Placeholder
</div>
<div className="bg-white/40 backdrop-blur-sm p-6 rounded-xl border border-sky-100 h-48 flex items-center justify-center text-sky-300">
Widget Placeholder
</div>
</div>
</div>
</main>
</div>
);
}
4 changes: 4 additions & 0 deletions Lb-web/src/features/project/services/projectService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,9 @@ export const projectService = {
getProjects: async (): Promise<Project[]> => {
const response = await api.get('/protected/project/projects');
return response.data.data;
},
getProjectById: async (id: string): Promise<Project> => {
const response = await api.get(`/protected/project/${id}`);
return response.data.data;
}
};
5 changes: 5 additions & 0 deletions Lb-web/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ProtectedRoute, PublicRoute, AdminRoute } from './features/Login/compon
import SettingsLayout from './features/admin-setting/layout/SettingsLayout.tsx'
import UserManagement from './features/admin-setting/pages/UserManagement.tsx'
import Appearance from './features/admin-setting/pages/Appearance.tsx'
import ProjectDashboard from './features/project/pages/ProjectDashboard.tsx'

const router = createBrowserRouter([
{
Expand Down Expand Up @@ -60,6 +61,10 @@ const router = createBrowserRouter([
element: <Navigate to="appearance" replace />
}
]
},
{
path: 'project/:id',
element: <ProjectDashboard />
}
]
}
Expand Down
26 changes: 14 additions & 12 deletions Lb-web/src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,22 @@ export default function Home() {
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{projects.map((project: { id: number; name: string; desc: string; created_at: string }) => (
<div key={project.id} className="bg-white/60 backdrop-blur-sm p-6 rounded-2xl shadow-sm border border-sky-100 hover:shadow-md transition-all cursor-pointer group">
<div className="h-12 w-12 bg-sky-100 rounded-xl flex items-center justify-center mb-4 group-hover:scale-110 transition-transform duration-200">
<div className="text-sky-600 font-bold text-xl">
{project.name.charAt(0).toUpperCase()}
<Link to={`/project/${project.id}`} key={project.id}>
<div className="bg-white/60 backdrop-blur-sm p-6 rounded-2xl shadow-sm border border-sky-100 hover:shadow-md transition-all cursor-pointer group h-full">
<div className="h-12 w-12 bg-sky-100 rounded-xl flex items-center justify-center mb-4 group-hover:scale-110 transition-transform duration-200">
<div className="text-sky-600 font-bold text-xl">
{project.name.charAt(0).toUpperCase()}
</div>
</div>
<h3 className="text-xl font-semibold text-sky-900 mb-2 truncate">{project.name}</h3>
<p className="text-sky-600/80 mb-4 line-clamp-2 h-12 text-sm">
{project.desc || "No description provided."}
</p>
<div className="text-xs text-sky-400 font-medium">
Created {new Date(project.created_at).toLocaleDateString()}
</div>
</div>
<h3 className="text-xl font-semibold text-sky-900 mb-2 truncate">{project.name}</h3>
<p className="text-sky-600/80 mb-4 line-clamp-2 h-12 text-sm">
{project.desc || "No description provided."}
</p>
<div className="text-xs text-sky-400 font-medium">
Created {new Date(project.created_at).toLocaleDateString()}
</div>
</div>
</Link>
))}
</div>
</div>
Expand Down
Loading