Skip to content

Commit 39bc529

Browse files
authored
Merge pull request #44 from Arjuna-Ragil/feat/40-project-dash-design
feat: Project internal dashboard added with data fetch
2 parents 2acab85 + 931fb1c commit 39bc529

10 files changed

Lines changed: 171 additions & 13 deletions

File tree

Internal/adapters/repository/projectRepo.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,11 @@ func (pr *ProjectRepo) FetchProjects(projects []domain.Project) ([]domain.Projec
2525
}
2626
return projects, nil
2727
}
28+
29+
func (pr *ProjectRepo) ProjectById(id string) (*domain.Project, error) {
30+
var project domain.Project
31+
if err := pr.DB.Gorm.First(&project, id).Error; err != nil {
32+
return nil, err
33+
}
34+
return &project, nil
35+
}

Internal/api/Routes/routev1.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ func SetupRouterV1(r *gin.Engine, deps Deps) {
5151
project := protected.Group("/project")
5252
{
5353
project.GET("/projects", deps.Project.GetAllProjectHandler)
54+
project.GET("/:projectid", deps.Project.GetProjectHandler)
5455
}
5556
}
5657
}

Internal/api/handlers/projectHandler.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,19 @@ func (ph *ProjectHandler) GetAllProjectHandler(c *gin.Context) {
6161
"data": projects,
6262
})
6363
}
64+
65+
func (ph *ProjectHandler) GetProjectHandler(c *gin.Context) {
66+
projectId := c.Param("projectid")
67+
project, err := ph.ProjectService.GetProjectById(projectId)
68+
if err != nil {
69+
c.JSON(500, gin.H{
70+
"message": "failed to get project",
71+
"data": err.Error(),
72+
})
73+
return
74+
}
75+
c.JSON(200, gin.H{
76+
"message": "success in fetching project",
77+
"data": project,
78+
})
79+
}

Internal/api/middleware/auth.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ func AuthMiddleware(userRepo *repository.UserRepository, cfg *config.Config) gin
2727
"message": "Authorization header not found",
2828
"data": err.Error(),
2929
})
30+
c.Abort()
3031
return
3132
}
3233
parts := strings.Split(authHeader, " ")

Internal/core/services/projectService.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,11 @@ func (ps *ProjectService) GetProjects(Role string) ([]domain.Project, error) {
4545
}
4646
return project, nil
4747
}
48+
49+
func (ps *ProjectService) GetProjectById(id string) (*domain.Project, error) {
50+
project, err := ps.ProjectRepo.ProjectById(id)
51+
if err != nil {
52+
return nil, err
53+
}
54+
return project, nil
55+
}

Lb-web/src/features/project/components/CreateProjectModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useState } from "react";
22
import { Button } from "@/components/ui/button";
33
import { Input } from "@/components/ui/input";
4-
import { Textarea } from "@/components/ui/textarea";
4+
import { Textarea } from "../../../components/ui/textarea";
55
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
66
import { Label } from "@/components/ui/label";
77
import { useAuth } from "@/features/Login/context/AuthContext";
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import { useEffect, useState } from "react";
2+
import { useParams, Link } from "react-router";
3+
import { projectService } from "../services/projectService";
4+
import { Button } from "@/components/ui/button";
5+
import { ArrowLeft, LayoutDashboard, Settings } from "lucide-react";
6+
7+
8+
export default function ProjectDashboard() {
9+
const { id } = useParams<{ id: string }>();
10+
const [project, setProject] = useState<{ id: number; name: string; desc: string; created_at: string } | null>(null);
11+
const [isLoading, setIsLoading] = useState(true);
12+
const [error, setError] = useState<string | null>(null);
13+
14+
useEffect(() => {
15+
const fetchProject = async () => {
16+
if (!id) return;
17+
setIsLoading(true);
18+
try {
19+
const data = await projectService.getProjectById(id);
20+
setProject(data);
21+
} catch (err: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
22+
setError("Failed to load project details.");
23+
console.error(err);
24+
} finally {
25+
setIsLoading(false);
26+
}
27+
};
28+
29+
fetchProject();
30+
}, [id]);
31+
32+
if (isLoading) {
33+
return (
34+
<div className="flex items-center justify-center h-screen bg-slate-50">
35+
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-sky-600"></div>
36+
</div>
37+
);
38+
}
39+
40+
if (error || !project) {
41+
return (
42+
<div className="flex flex-col items-center justify-center h-screen bg-slate-50">
43+
<h2 className="text-2xl font-bold text-sky-900 mb-4">{error || "Project not found"}</h2>
44+
<Link to="/">
45+
<Button variant="outline">
46+
<ArrowLeft className="mr-2 h-4 w-4" />
47+
Back to Home
48+
</Button>
49+
</Link>
50+
</div>
51+
);
52+
}
53+
54+
return (
55+
<div className="flex flex-col h-screen bg-slate-50 relative overflow-hidden">
56+
{/* Subtle Background */}
57+
<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>
58+
59+
{/* Sidebar (Mockup for now, or just a simple header) */}
60+
{/* Let's stick to a clean layout similar to Home but focused on the project */}
61+
62+
<header className="flex items-center justify-between px-6 py-4 bg-white/50 backdrop-blur-md border-b border-sky-100 z-20">
63+
<div className="flex items-center space-x-4">
64+
<Link to="/">
65+
<Button variant="ghost" size="icon" className="text-sky-700 hover:text-sky-900 hover:bg-sky-100">
66+
<ArrowLeft className="h-5 w-5" />
67+
</Button>
68+
</Link>
69+
<div>
70+
<h1 className="text-xl font-bold text-sky-900 tracking-tight">{project.name}</h1>
71+
<p className="text-xs text-sky-500 font-medium">Project Dashboard</p>
72+
</div>
73+
</div>
74+
<div className="flex items-center space-x-2">
75+
<Button variant="ghost" size="icon" className="text-sky-700 hover:text-sky-900 hover:bg-sky-100">
76+
<LayoutDashboard className="h-5 w-5" />
77+
</Button>
78+
<Button variant="ghost" size="icon" className="text-sky-700 hover:text-sky-900 hover:bg-sky-100">
79+
<Settings className="h-5 w-5" />
80+
</Button>
81+
</div>
82+
</header>
83+
84+
<main className="flex-1 p-8 z-10 overflow-y-auto">
85+
<div className="max-w-5xl mx-auto">
86+
<div className="bg-white/60 backdrop-blur-sm p-8 rounded-2xl shadow-sm border border-sky-100 mb-8">
87+
<h2 className="text-2xl font-semibold text-sky-900 mb-4">Welcome to {project.name}</h2>
88+
<p className="text-sky-700/80 leading-relaxed mb-6">
89+
{project.desc || "No description provided for this project."}
90+
</p>
91+
<div className="flex items-center space-x-4 text-sm text-sky-500">
92+
<span>Created on {new Date(project.created_at).toLocaleDateString()}</span>
93+
{/* Potential place for more stats like '3 Members', '5 Tasks', etc. */}
94+
</div>
95+
</div>
96+
97+
{/* Placeholder for future dashboard widgets */}
98+
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
99+
<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">
100+
Widget Placeholder
101+
</div>
102+
<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">
103+
Widget Placeholder
104+
</div>
105+
<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">
106+
Widget Placeholder
107+
</div>
108+
</div>
109+
</div>
110+
</main>
111+
</div>
112+
);
113+
}

Lb-web/src/features/project/services/projectService.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,9 @@ export const projectService = {
2323
getProjects: async (): Promise<Project[]> => {
2424
const response = await api.get('/protected/project/projects');
2525
return response.data.data;
26+
},
27+
getProjectById: async (id: string): Promise<Project> => {
28+
const response = await api.get(`/protected/project/${id}`);
29+
return response.data.data;
2630
}
2731
};

Lb-web/src/main.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ProtectedRoute, PublicRoute, AdminRoute } from './features/Login/compon
1111
import SettingsLayout from './features/admin-setting/layout/SettingsLayout.tsx'
1212
import UserManagement from './features/admin-setting/pages/UserManagement.tsx'
1313
import Appearance from './features/admin-setting/pages/Appearance.tsx'
14+
import ProjectDashboard from './features/project/pages/ProjectDashboard.tsx'
1415

1516
const router = createBrowserRouter([
1617
{
@@ -60,6 +61,10 @@ const router = createBrowserRouter([
6061
element: <Navigate to="appearance" replace />
6162
}
6263
]
64+
},
65+
{
66+
path: 'project/:id',
67+
element: <ProjectDashboard />
6368
}
6469
]
6570
}

Lb-web/src/pages/Home.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -121,20 +121,22 @@ export default function Home() {
121121
</div>
122122
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
123123
{projects.map((project: { id: number; name: string; desc: string; created_at: string }) => (
124-
<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">
125-
<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">
126-
<div className="text-sky-600 font-bold text-xl">
127-
{project.name.charAt(0).toUpperCase()}
124+
<Link to={`/project/${project.id}`} key={project.id}>
125+
<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">
126+
<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">
127+
<div className="text-sky-600 font-bold text-xl">
128+
{project.name.charAt(0).toUpperCase()}
129+
</div>
130+
</div>
131+
<h3 className="text-xl font-semibold text-sky-900 mb-2 truncate">{project.name}</h3>
132+
<p className="text-sky-600/80 mb-4 line-clamp-2 h-12 text-sm">
133+
{project.desc || "No description provided."}
134+
</p>
135+
<div className="text-xs text-sky-400 font-medium">
136+
Created {new Date(project.created_at).toLocaleDateString()}
128137
</div>
129138
</div>
130-
<h3 className="text-xl font-semibold text-sky-900 mb-2 truncate">{project.name}</h3>
131-
<p className="text-sky-600/80 mb-4 line-clamp-2 h-12 text-sm">
132-
{project.desc || "No description provided."}
133-
</p>
134-
<div className="text-xs text-sky-400 font-medium">
135-
Created {new Date(project.created_at).toLocaleDateString()}
136-
</div>
137-
</div>
139+
</Link>
138140
))}
139141
</div>
140142
</div>

0 commit comments

Comments
 (0)