Skip to content

Commit e047d1a

Browse files
author
raineer
committed
Fix Hire Codevs Showcase Qualification Filter
fix(in-house): enhance table spacing per team lead feedbackfix(in-house): improve table spacing and alignment
1 parent 4bad333 commit e047d1a

3 files changed

Lines changed: 167 additions & 148 deletions

File tree

‎apps/codebility/app/(marketing)/profiles/_components/CodevCard.tsx‎

Lines changed: 47 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useMemo, useState } from "react";
66
import Image from "next/image";
77
import Link from "next/link";
88
import CodevBadge from "@/components/CodevBadge";
9+
import { getTotalCodevPoints } from "@/utils/codev-qualification";
910
import {
1011
AnimatePresence,
1112
motion,
@@ -31,41 +32,32 @@ interface Props {
3132
color: string;
3233
}
3334

34-
const STATUS_CONFIG: Record<
35-
InternalStatus,
36-
{ label: string; className: string }
37-
> = {
35+
const STATUS_CONFIG = {
3836
TRAINING: {
3937
label: "Training",
40-
className:
41-
"bg-yellow-500/20 backdrop-blur-sm text-yellow-200 border border-yellow-500/30 dark:bg-yellow-500/10 dark:text-yellow-300",
38+
className: "bg-yellow-500/20 backdrop-blur-sm text-yellow-200 border border-yellow-500/30 dark:bg-yellow-500/10 dark:text-yellow-300",
4239
},
4340
GRADUATED: {
4441
label: "Graduated",
45-
className:
46-
"bg-green-500/20 backdrop-blur-sm text-green-200 border border-green-500/30 dark:bg-green-500/10 dark:text-green-300",
42+
className: "bg-green-500/20 backdrop-blur-sm text-green-200 border border-green-500/30 dark:bg-green-500/10 dark:text-green-300",
4743
},
4844
INACTIVE: {
4945
label: "Inactive",
50-
className:
51-
"bg-gray-500/20 backdrop-blur-sm text-gray-200 border border-gray-500/30 dark:bg-gray-500/10 dark:text-gray-300",
46+
className: "bg-gray-500/20 backdrop-blur-sm text-gray-200 border border-gray-500/30 dark:bg-gray-500/10 dark:text-gray-300",
5247
},
5348
MENTOR: {
5449
label: "Mentor",
55-
className:
56-
"bg-purple-500/20 backdrop-blur-sm text-purple-200 border border-purple-500/30 dark:bg-purple-500/10 dark:text-purple-300",
50+
className: "bg-purple-500/20 backdrop-blur-sm text-purple-200 border border-purple-500/30 dark:bg-purple-500/10 dark:text-purple-300",
5751
},
5852
ADMIN: {
5953
label: "Admin",
60-
className:
61-
"bg-blue-500/20 backdrop-blur-sm text-blue-200 border border-blue-500/30 dark:bg-blue-500/10 dark:text-blue-300",
54+
className: "bg-blue-500/20 backdrop-blur-sm text-blue-200 border border-blue-500/30 dark:bg-blue-500/10 dark:text-blue-300",
6255
},
6356
DEPLOYED: {
6457
label: "Deployed",
65-
className:
66-
"bg-indigo-500/20 backdrop-blur-sm text-indigo-200 border border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-300",
58+
className: "bg-indigo-500/20 backdrop-blur-sm text-indigo-200 border border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-300",
6759
},
68-
};
60+
} as const;
6961

7062
const CodevCard = ({ codev, color }: Props) => {
7163
const [hovered, setHovered] = useState(false);
@@ -86,13 +78,10 @@ const CodevCard = ({ codev, color }: Props) => {
8678
};
8779

8880
const internalStatus = codev.internal_status || "MENTOR";
89-
const statusConfig =
90-
STATUS_CONFIG[internalStatus as InternalStatus] || STATUS_CONFIG.MENTOR;
81+
const statusConfig = STATUS_CONFIG[internalStatus as keyof typeof STATUS_CONFIG] || STATUS_CONFIG.MENTOR;
9182

9283
const filteredLevel = useMemo(() => {
93-
return codev.level &&
94-
codev.codev_points &&
95-
Array.isArray(codev.codev_points)
84+
return codev.level && codev.codev_points && Array.isArray(codev.codev_points)
9685
? Object.fromEntries(
9786
Object.entries(codev.level)
9887
.filter(([skillCategoryId, levelValue]) => {
@@ -104,160 +93,82 @@ const CodevCard = ({ codev, color }: Props) => {
10493
);
10594
})
10695
.sort(([skillCategoryIdA], [skillCategoryIdB]) => {
107-
const pointsA =
108-
codev.codev_points!.find(
109-
(point) => point?.skill_category_id === skillCategoryIdA,
110-
)?.points || 0;
111-
const pointsB =
112-
codev.codev_points!.find(
113-
(point) => point?.skill_category_id === skillCategoryIdB,
114-
)?.points || 0;
96+
const pointsA = codev.codev_points!.find((point) => point?.skill_category_id === skillCategoryIdA)?.points || 0;
97+
const pointsB = codev.codev_points!.find((point) => point?.skill_category_id === skillCategoryIdB)?.points || 0;
11598
return pointsB - pointsA;
11699
}),
117100
)
118101
: {};
119102
}, [codev.level, codev.codev_points]);
120103

121-
// Line 119: Enhanced card wrapper with click redirect
104+
const totalPoints = getTotalCodevPoints(codev.codev_points);
105+
122106
return (
123-
<div
124-
className="h-64"
125-
onMouseEnter={() => setHovered(true)}
126-
onMouseLeave={() => setHovered(false)}
127-
>
128-
<div
129-
onClick={() =>
130-
(window.location.href = `https://www.codebility.tech/profiles/${codev.id}`)
131-
}
132-
className="group relative flex h-full w-full cursor-pointer flex-col items-center justify-between rounded-lg border border-white/20 bg-white/10 p-4 shadow-2xl backdrop-blur-md transition-all duration-300 hover:scale-[1.02] hover:bg-white/20 dark:border-white/10 dark:bg-white/5 dark:hover:bg-white/10"
133-
>
134-
{/* Background decoration */}
107+
<div className="h-64" onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)}>
108+
<div onClick={() => window.location.href = `https://www.codebility.tech/profiles/${codev.id}`} className="group relative flex h-full w-full flex-col items-center justify-between rounded-lg border border-white/20 bg-white/10 p-4 shadow-2xl backdrop-blur-md transition-all duration-300 cursor-pointer hover:bg-white/20 hover:scale-[1.02] dark:border-white/10 dark:bg-white/5 dark:hover:bg-white/10">
135109
<div className="from-customBlue-50/30 dark:from-customBlue-950/10 absolute inset-0 bg-gradient-to-br to-purple-50/30 opacity-0 transition-opacity duration-300 group-hover:opacity-100 dark:to-purple-950/10" />
136110
<div className="absolute -right-4 -top-4 h-32 w-32 rounded-full bg-gradient-to-br from-yellow-400/10 to-orange-400/10 opacity-0 blur-2xl transition-opacity duration-500 group-hover:opacity-100" />
137111
<div className="grid w-full grid-cols-4 gap-2">
138112
<div className="col-span-1" />
139113
<div className="relative col-span-2 flex justify-center">
140-
<Image
141-
alt={`${codev.first_name} Avatar`}
142-
src={
143-
codev.image_url &&
144-
(codev.image_url.startsWith("http") ||
145-
codev.image_url.startsWith("/"))
146-
? codev.image_url
147-
: "/assets/svgs/icon-codebility-black.svg"
148-
}
149-
width={70}
150-
height={70}
151-
className={`${color} h-[70px] w-[70px] rounded-full bg-cover object-cover p-0.5`}
152-
onMouseMove={handleMouseMove}
153-
onError={(e) => {
154-
console.error(
155-
`Failed to load image for ${codev.first_name}: ${codev.image_url}`,
156-
);
157-
e.currentTarget.src = "/assets/svgs/icon-codebility-black.svg";
158-
}}
159-
/>
114+
<div className="absolute -top-2 -right-2 z-20">
115+
<TooltipProvider>
116+
<Tooltip delayDuration={100}>
117+
<TooltipTrigger asChild>
118+
<div className="flex items-center gap-1 rounded-full border border-yellow-500/30 bg-gradient-to-br from-yellow-500/20 to-orange-500/20 px-2.5 py-1 shadow-lg backdrop-blur-sm transition-all duration-300 hover:scale-110 hover:from-yellow-500/30 hover:to-orange-500/30">
119+
<span className="text-xs font-bold text-yellow-200">⭐</span>
120+
<span className="text-xs font-bold text-yellow-200">{totalPoints}</span>
121+
</div>
122+
</TooltipTrigger>
123+
<TooltipContent className="border border-white/20 bg-white/10 px-3 py-2 text-xs text-white backdrop-blur-md dark:border-white/10 dark:bg-white/5">
124+
<div className="flex flex-col gap-1">
125+
<p className="font-semibold">Total Skill Points</p>
126+
<p className="text-gray-200">Qualification: ≥100 points</p>
127+
</div>
128+
</TooltipContent>
129+
</Tooltip>
130+
</TooltipProvider>
131+
</div>
132+
<Image alt={`${codev.first_name} Avatar`} src={codev.image_url && (codev.image_url.startsWith("http") || codev.image_url.startsWith("/")) ? codev.image_url : "/assets/svgs/icon-codebility-black.svg"} width={70} height={70} className={`${color} h-[70px] w-[70px] rounded-full bg-cover object-cover p-0.5`} onMouseMove={handleMouseMove} onError={(e) => { console.error(`Failed to load image for ${codev.first_name}: ${codev.image_url}`); e.currentTarget.src = "/assets/svgs/icon-codebility-black.svg"; }} />
160133
<AnimatePresence>
161134
{hovered && internalStatus && (
162-
<motion.div
163-
initial={{ opacity: 0, y: 20, scale: 0.6 }}
164-
animate={{
165-
opacity: 1,
166-
y: 0,
167-
scale: 1,
168-
transition: {
169-
type: "spring",
170-
stiffness: 260,
171-
damping: 10,
172-
},
173-
}}
174-
exit={{ opacity: 0, y: 20, scale: 0.6 }}
175-
style={{
176-
translateX: translateX,
177-
rotate: rotate,
178-
whiteSpace: "nowrap",
179-
}}
180-
className={cn(
181-
"absolute -top-8 right-1/2 z-50 flex -translate-x-1/2 transform flex-col items-center justify-center rounded-lg px-4 py-2 shadow-xl",
182-
statusConfig.className,
183-
)}
184-
>
185-
<div className="relative z-30 text-base">
186-
{statusConfig.label}
187-
</div>
135+
<motion.div initial={{ opacity: 0, y: 20, scale: 0.6 }} animate={{ opacity: 1, y: 0, scale: 1, transition: { type: "spring", stiffness: 260, damping: 10 } }} exit={{ opacity: 0, y: 20, scale: 0.6 }} style={{ translateX: translateX, rotate: rotate, whiteSpace: "nowrap" }} className={cn("absolute -top-8 right-1/2 z-50 flex -translate-x-1/2 transform flex-col items-center justify-center rounded-lg px-4 py-2 shadow-xl", statusConfig.className)}>
136+
<div className="relative z-30 text-base">{statusConfig.label}</div>
188137
</motion.div>
189138
)}
190139
</AnimatePresence>
191140
<div className="absolute bottom-[1px] right-[1px]">
192-
<p
193-
className={cn(
194-
"rounded-full border-2 p-2 text-[9px]",
195-
codev.availability_status
196-
? "border-green-600 bg-green-500"
197-
: "border-red-600 bg-red-500",
198-
)}
199-
></p>
141+
<p className={cn("rounded-full border-2 p-2 text-[9px]", codev.availability_status ? "border-green-600 bg-green-500" : "border-red-600 bg-red-500")}></p>
200142
</div>
201143
</div>
202144
<div className="col-span-1 flex justify-end">
203145
<TooltipProvider>
204146
<Tooltip delayDuration={100}>
205147
<TooltipTrigger asChild>
206-
{/* Line 217: Added stopPropagation to prevent card click when button is clicked */}
207-
<Link
208-
href={`/profiles/${codev.id}`}
209-
target="_blank"
210-
onClick={(e) => e.stopPropagation()}
211-
>
212-
<Button
213-
variant="outline"
214-
size="icon"
215-
className="h-8 w-8 rounded-full border-white/30 bg-white/20 backdrop-blur-sm transition-all duration-300 hover:bg-white/30 dark:border-white/20 dark:bg-white/10 dark:hover:bg-white/20"
216-
>
148+
<Link href={`/profiles/${codev.id}`} target="_blank" onClick={(e) => e.stopPropagation()}>
149+
<Button variant="outline" size="icon" className="h-8 w-8 rounded-full border-white/30 bg-white/20 backdrop-blur-sm transition-all duration-300 hover:bg-white/30 dark:border-white/20 dark:bg-white/10 dark:hover:bg-white/20">
217150
<BookOpenIcon className="h-3.5 w-3.5" />
218151
</Button>
219152
</Link>
220153
</TooltipTrigger>
221-
<TooltipContent className="border border-white/20 bg-white/10 px-2 py-1 text-xs text-white backdrop-blur-md dark:border-white/10 dark:bg-white/5">
222-
Read Bio
223-
</TooltipContent>
154+
<TooltipContent className="border border-white/20 bg-white/10 px-2 py-1 text-xs text-white backdrop-blur-md dark:border-white/10 dark:bg-white/5">Read Bio</TooltipContent>
224155
</Tooltip>
225156
</TooltipProvider>
226157
</div>
227158
</div>
228159
<div className="flex flex-col items-center gap-0.5">
229160
<div className="flex flex-col gap-1 text-center">
230-
<p className="md:text-md relative z-10 text-sm capitalize text-white dark:text-white lg:text-base">
231-
{codev.first_name} {codev.last_name}
232-
</p>
233-
{codev.display_position ? (
234-
<p className="relative z-10 text-sm text-gray-200 dark:text-gray-300">
235-
{codev.display_position}
236-
</p>
237-
) : (
238-
<div className="relative z-10 text-sm lg:text-base">&nbsp;</div>
239-
)}
161+
<p className="md:text-md relative z-10 text-sm capitalize text-white dark:text-white lg:text-base">{codev.first_name} {codev.last_name}</p>
162+
{codev.display_position ? (<p className="relative z-10 text-sm text-gray-200 dark:text-gray-300">{codev.display_position}</p>) : (<div className="relative z-10 text-sm lg:text-base">&nbsp;</div>)}
240163
</div>
241164
</div>
242165
<div className="flex min-h-[20px] items-center justify-center">
243-
{filteredLevel && Object.keys(filteredLevel).length > 0 ? (
244-
<CodevBadge
245-
level={filteredLevel}
246-
className="transition-transform group-hover:scale-100"
247-
/>
248-
) : null}
249-
</div>
250-
<div
251-
className="relative z-20"
252-
onMouseEnter={(e) => e.stopPropagation()}
253-
onMouseLeave={(e) => e.stopPropagation()}
254-
onClick={(e) => e.stopPropagation()}
255-
>
256-
<CodevHireCodevButton codevId={codev.id} />
166+
{filteredLevel && Object.keys(filteredLevel).length > 0 ? (<CodevBadge level={filteredLevel} className="transition-transform group-hover:scale-100" />) : null}
257167
</div>
168+
<CodevHireCodevButton codevId={codev.id}/>
258169
</div>
259170
</div>
260171
);
261172
};
262173

263-
export default CodevCard;
174+
export default CodevCard;

‎apps/codebility/app/(marketing)/profiles/page.tsx‎

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ import { Suspense } from "react";
22
import { UsersSkeleton } from "@/components/ui/skeleton/UsersSkeleton";
33
import { getCodevs } from "@/lib/server/codev.service";
44
import { Codev } from "@/types/home/codev";
5-
import {
6-
getPrioritizedAndFilteredCodevs,
7-
prioritizeCodevs,
8-
} from "@/utils/codev-priority"; // Import the utility
5+
import { prioritizeCodevs } from "@/utils/codev-priority"; // ← CHANGED: Only import prioritizeCodevs
6+
import { getQualifiedCodevs } from "@/utils/codev-qualification";
97

108
import Section from "../_shared/CodevsSection";
119
import CodevContainer from "./_components/CodevContainer";
@@ -16,22 +14,35 @@ export const dynamic = "force-dynamic";
1614
export const revalidate = 0;
1715

1816
export default async function Profiles() {
17+
// Fetch all passed codevs (includes admins, mentors, codevs)
1918
const [{ data: allCodevs, error }] = await Promise.all([
20-
getCodevs({ filters: { role_id: 10 } }), // show only Codevs with role_id 10 which is Codev
19+
getCodevs({ filters: { application_status: "passed" } }),
2120
]);
2221

2322
if (error) {
2423
throw new Error("Failed to fetch profiles data");
2524
}
2625

26+
// Convert to array safely
2727
const codevsArray: Codev[] = Array.isArray(allCodevs) ? allCodevs : [];
2828

29-
// Use the utility function with filterAdminAndFailed set to true
30-
const sortedCodevs = getPrioritizedAndFilteredCodevs(
31-
codevsArray,
32-
{ activeStatus: ["active"] },
33-
true,
34-
);
29+
// ✅ STEP 1: Filter by qualification criteria (100+ points, TRAINING/GRADUATED/NULL, available)
30+
// This handles ALL filtering logic - no need for additional filters
31+
const qualifiedCodevs = getQualifiedCodevs(codevsArray);
32+
33+
// ✅ STEP 2: Apply priority sorting only (no additional filtering)
34+
// We already filtered everything in getQualifiedCodevs(), so just prioritize by:
35+
// - Active projects > No projects
36+
// - Available > Unavailable
37+
const sortedCodevs = prioritizeCodevs(qualifiedCodevs); // ← CHANGED: Direct prioritization
38+
39+
// Debug logging in development
40+
if (process.env.NODE_ENV === "development") {
41+
console.log("📊 PROFILES PAGE STATS:");
42+
console.log(` Total codevs fetched: ${codevsArray.length}`);
43+
console.log(` Qualified codevs: ${qualifiedCodevs.length}`);
44+
console.log(` Final displayed: ${sortedCodevs.length}`);
45+
}
3546

3647
return (
3748
<>
@@ -54,4 +65,4 @@ export default async function Profiles() {
5465
<CodevHireCodevModal />
5566
</>
5667
);
57-
}
68+
}

0 commit comments

Comments
 (0)