Skip to content

Commit 5aaf518

Browse files
committed
my-team: added memers modal
1 parent 8352e44 commit 5aaf518

4 files changed

Lines changed: 654 additions & 3 deletions

File tree

‎apps/codebility/app/home/my-team/[projectId]/_components/CompactMemberGrid.tsx‎

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Mail, Phone, Calendar, MapPin, Briefcase, Trophy } from "lucide-react";
88
import { multipleMoveApplicantToOnboardingAction } from "@/app/home/applicants/_service/action";
99
const ATTENDANCE_POINTS_PER_DAY = 2;
1010
import { getTeamMonthlyAttendancePoints } from "../actions";
11+
import MemberDetailModal from "../../_components/MemberDetailModal";
1112

1213
interface CompactMemberGridProps {
1314
members: SimpleMemberData[];
@@ -27,6 +28,8 @@ const CompactMemberGrid = ({ members, teamLead, projectId }: CompactMemberGridPr
2728
const [memberPoints, setMemberPoints] = useState<MemberPoints>({});
2829
const [isLoadingPoints, setIsLoadingPoints] = useState(false); // Start as false, load on demand
2930
const [monthlyAttendancePoints, setMonthlyAttendancePoints] = useState<{ uniqueCodevIdsPresentDays: { codevId: string; presentDays: number }[]; }>({ uniqueCodevIdsPresentDays: [] });
31+
const [selectedMember, setSelectedMember] = useState<SimpleMemberData | null>(null);
32+
const [isModalOpen, setIsModalOpen] = useState(false);
3033

3134
// Load points for all members - made more efficient and optional
3235
const loadAllPoints = async () => {
@@ -44,7 +47,7 @@ const CompactMemberGrid = ({ members, teamLead, projectId }: CompactMemberGridPr
4447
try {
4548
const response = await fetch(`/api/codev/${member.id}/points`);
4649
if (!response.ok) throw new Error('Failed to fetch');
47-
const data = await response.json();
50+
const data = await response.json() as { totalPoints?: number; attendancePoints?: number };
4851
return { memberId: member.id, data };
4952
} catch (error) {
5053
console.warn(`Failed to load points for ${member.id}:`, error);
@@ -95,6 +98,16 @@ const CompactMemberGrid = ({ members, teamLead, projectId }: CompactMemberGridPr
9598
const hasPointsData = Object.keys(memberPoints).length > 0;
9699
const showLoadPointsButton = !hasPointsData && !isLoadingPoints && allMembers.length > 0;
97100

101+
const handleMemberClick = (member: SimpleMemberData) => {
102+
setSelectedMember(member);
103+
setIsModalOpen(true);
104+
};
105+
106+
const handleCloseModal = () => {
107+
setIsModalOpen(false);
108+
setSelectedMember(null);
109+
};
110+
98111
return (
99112
<div className="space-y-4">
100113
{/* Load Points Button */}
@@ -127,7 +140,8 @@ const CompactMemberGrid = ({ members, teamLead, projectId }: CompactMemberGridPr
127140
return (
128141
<div
129142
key={member.id}
130-
className="relative rounded-lg border border-gray-200 bg-white p-4 shadow-sm transition-all duration-200 hover:shadow-md dark:border-gray-700 dark:bg-gray-800 overflow-hidden"
143+
onClick={() => handleMemberClick(member)}
144+
className="relative rounded-lg border border-gray-200 bg-white p-4 shadow-sm transition-all duration-200 hover:shadow-md hover:scale-105 hover:border-blue-300 dark:border-gray-700 dark:bg-gray-800 dark:hover:border-blue-600 overflow-hidden cursor-pointer"
131145
>
132146
{/* Status indicator */}
133147
<div className="absolute top-2 right-2 h-2 w-2 rounded-full bg-green-500" title="Active" />
@@ -188,7 +202,7 @@ const CompactMemberGrid = ({ members, teamLead, projectId }: CompactMemberGridPr
188202
<span className="text-gray-500">Total Points:</span>
189203
{memberHasPoints ? (
190204
<span className="font-semibold text-gray-900 dark:text-white">
191-
{memberPoints[member.id].totalPoints}
205+
{memberPoints[member.id]?.totalPoints || 0}
192206
</span>
193207
) : (
194208
<div className="w-8 h-3 bg-gray-200 animate-pulse rounded"></div>
@@ -221,6 +235,15 @@ const CompactMemberGrid = ({ members, teamLead, projectId }: CompactMemberGridPr
221235
);
222236
})}
223237
</div>
238+
239+
{/* Member Detail Modal */}
240+
<MemberDetailModal
241+
isOpen={isModalOpen}
242+
member={selectedMember}
243+
isTeamLead={selectedMember?.id === teamLead?.id}
244+
projectId={projectId}
245+
onClose={handleCloseModal}
246+
/>
224247
</div>
225248
);
226249
};
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
"use client";
2+
3+
import { useState, useEffect } from "react";
4+
import { CheckCircle2, Circle, Calendar } from "lucide-react";
5+
6+
interface ChecklistItem {
7+
id: string;
8+
title: string;
9+
description: string;
10+
completed: boolean;
11+
dueDate?: string;
12+
priority: "low" | "medium" | "high";
13+
}
14+
15+
interface MemberChecklistProps {
16+
memberId: string;
17+
projectId: string;
18+
}
19+
20+
const MemberChecklist = ({ memberId, projectId }: MemberChecklistProps) => {
21+
const [checklistItems, setChecklistItems] = useState<ChecklistItem[]>([]);
22+
const [isLoading, setIsLoading] = useState(true);
23+
24+
useEffect(() => {
25+
loadChecklistItems();
26+
}, [memberId, projectId]);
27+
28+
const loadChecklistItems = async () => {
29+
if (!memberId || !projectId) return;
30+
31+
setIsLoading(true);
32+
try {
33+
// Mock data for now - in real implementation, this would fetch from API
34+
// You can replace this with actual API call: const response = await fetch(`/api/projects/${projectId}/members/${memberId}/checklist`);
35+
36+
// Simulate API delay
37+
await new Promise(resolve => setTimeout(resolve, 300));
38+
39+
setChecklistItems([
40+
{
41+
id: "1",
42+
title: "Complete onboarding documentation",
43+
description: "Fill out all required forms and complete the onboarding checklist",
44+
completed: true,
45+
priority: "high"
46+
},
47+
{
48+
id: "2",
49+
title: "Set up development environment",
50+
description: "Install required tools and configure local development setup",
51+
completed: true,
52+
priority: "high"
53+
},
54+
{
55+
id: "3",
56+
title: "Review project documentation",
57+
description: "Go through the project README, API docs, and architecture overview",
58+
completed: true,
59+
priority: "medium"
60+
},
61+
{
62+
id: "4",
63+
title: "Implement user authentication feature",
64+
description: "Create login/logout functionality with proper validation",
65+
completed: false,
66+
dueDate: "2025-10-20",
67+
priority: "high"
68+
},
69+
{
70+
id: "5",
71+
title: "Write unit tests for API endpoints",
72+
description: "Add comprehensive test coverage for all authentication endpoints",
73+
completed: false,
74+
dueDate: "2025-10-25",
75+
priority: "medium"
76+
},
77+
{
78+
id: "6",
79+
title: "Code review training session",
80+
description: "Attend the team code review best practices workshop",
81+
completed: false,
82+
dueDate: "2025-10-30",
83+
priority: "low"
84+
}
85+
]);
86+
} catch (error) {
87+
console.error("Error loading checklist items:", error);
88+
setChecklistItems([]);
89+
} finally {
90+
setIsLoading(false);
91+
}
92+
};
93+
const completedTasks = checklistItems.filter(item => item.completed).length;
94+
const totalTasks = checklistItems.length;
95+
const completionPercentage = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0;
96+
97+
const getPriorityColor = (priority: string) => {
98+
switch (priority) {
99+
case "high": return "text-red-600 bg-red-100 border-red-200";
100+
case "medium": return "text-yellow-600 bg-yellow-100 border-yellow-200";
101+
case "low": return "text-green-600 bg-green-100 border-green-200";
102+
default: return "text-gray-600 bg-gray-100 border-gray-200";
103+
}
104+
};
105+
106+
if (isLoading) {
107+
return (
108+
<div className="flex items-center justify-center py-8">
109+
<div className="text-center">
110+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-green-500 mx-auto mb-4"></div>
111+
<p className="text-gray-600 dark:text-gray-300">Loading checklist...</p>
112+
</div>
113+
</div>
114+
);
115+
}
116+
117+
if (checklistItems.length === 0) {
118+
return (
119+
<div className="flex items-center justify-center py-8">
120+
<div className="text-center">
121+
<CheckCircle2 className="h-12 w-12 text-gray-400 mx-auto mb-4" />
122+
<p className="text-gray-600 dark:text-gray-300">No checklist items available</p>
123+
</div>
124+
</div>
125+
);
126+
}
127+
128+
return (
129+
<div className="space-y-6">
130+
{/* Progress Overview */}
131+
<Section title="Progress Overview">
132+
<div className="flex items-center gap-4 p-4 rounded-lg bg-white/20 backdrop-blur-sm dark:bg-white/10 border border-white/30 dark:border-white/20">
133+
<div className="text-center">
134+
<div className="text-3xl font-bold text-green-600 dark:text-green-400">
135+
{completionPercentage}%
136+
</div>
137+
<div className="text-sm text-gray-500">Complete</div>
138+
</div>
139+
<div className="flex-1">
140+
<div className="flex justify-between text-sm text-gray-600 dark:text-gray-400 mb-2">
141+
<span>{completedTasks} of {totalTasks} tasks completed</span>
142+
</div>
143+
<div className="w-full bg-gray-200 rounded-full h-2">
144+
<div
145+
className="bg-green-500 h-2 rounded-full transition-all duration-300"
146+
style={{ width: `${completionPercentage}%` }}
147+
></div>
148+
</div>
149+
</div>
150+
</div>
151+
</Section>
152+
153+
{/* Task List */}
154+
<Section title="Task List">
155+
<div className="space-y-3">
156+
{checklistItems.map((item) => (
157+
<div
158+
key={item.id}
159+
className={`p-4 rounded-lg border transition-all duration-200 ${
160+
item.completed
161+
? 'bg-green-50 dark:bg-green-950/20 border-green-200 dark:border-green-800'
162+
: 'bg-white/20 backdrop-blur-sm dark:bg-white/10 border-white/30 dark:border-white/20'
163+
}`}
164+
>
165+
<div className="flex items-start gap-3">
166+
<div className="flex-shrink-0 mt-0.5">
167+
{item.completed ? (
168+
<CheckCircle2 className="h-5 w-5 text-green-600" />
169+
) : (
170+
<Circle className="h-5 w-5 text-gray-400" />
171+
)}
172+
</div>
173+
<div className="flex-1 min-w-0">
174+
<div className="flex items-start justify-between gap-2">
175+
<h4 className={`font-medium ${
176+
item.completed
177+
? 'text-green-800 dark:text-green-200 line-through'
178+
: 'text-gray-900 dark:text-white'
179+
}`}>
180+
{item.title}
181+
</h4>
182+
<span className={`px-2 py-1 text-xs font-medium rounded-full border ${getPriorityColor(item.priority)}`}>
183+
{item.priority}
184+
</span>
185+
</div>
186+
<p className={`text-sm mt-1 ${
187+
item.completed
188+
? 'text-green-700 dark:text-green-300'
189+
: 'text-gray-600 dark:text-gray-400'
190+
}`}>
191+
{item.description}
192+
</p>
193+
{item.dueDate && (
194+
<p className="text-xs text-gray-500 dark:text-gray-400 mt-2 flex items-center gap-1">
195+
<Calendar className="h-3 w-3" />
196+
Due: {new Date(item.dueDate).toLocaleDateString()}
197+
</p>
198+
)}
199+
</div>
200+
</div>
201+
</div>
202+
))}
203+
</div>
204+
</Section>
205+
</div>
206+
);
207+
};
208+
209+
// Helper Component
210+
const Section = ({
211+
title,
212+
children,
213+
}: {
214+
title: string;
215+
children: React.ReactNode;
216+
}) => (
217+
<div className="space-y-3">
218+
<h3 className="text-lg font-medium">{title}</h3>
219+
{children}
220+
</div>
221+
);
222+
223+
export default MemberChecklist;

0 commit comments

Comments
 (0)