Initial commit

This commit is contained in:
2026-08-07 23:53:15 +05:30
commit 90f14a73b2
222 changed files with 35000 additions and 0 deletions

View File

@@ -0,0 +1,246 @@
import React, { useState } from 'react';
import { cn } from '@/lib/utils';
import { Mail, Briefcase, MapPin, Star, BadgeCheck, Award, ChevronDown } from 'lucide-react';
import { Image } from '@/components/ui/image';
import EmployerInsights from '@/components/krow/talent/EmployerInsights';
import { getScoreBand, toFICO } from '@/lib/talentHome';
const PORTRAIT_PHOTOS = [
'photo-1500648767731-7d3514ad8b31',
'photo-1531123897727-8f129e1688ce',
'photo-1573496359142-b8d87734a5a2',
'photo-1487412720507-e7ab37603c6f',
'photo-1502685104226-ee3a97a2f2c8',
'photo-1544005313-94ddf0286df2',
'photo-1494790108377-be9c29b29330',
'photo-1438761681033-6461ffad8d80',
'photo-1517841905240-472988babdf9',
'photo-1463453091185-61582044d556',
];
function getPortraitSeed(name) {
let h = 0;
for (let i = 0; i < (name || '').length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;
return PORTRAIT_PHOTOS[h % PORTRAIT_PHOTOS.length];
}
function CareerGauge({ fico }) {
const MIN = 300;
const MAX = 850;
const value = Math.max(MIN, Math.min(MAX, fico || MIN));
const frac = (value - MIN) / (MAX - MIN);
const cx = 55;
const cy = 58;
const r = 46;
const theta = (180 - frac * 180) * (Math.PI / 180);
const xv = cx + r * Math.cos(theta);
const yv = cy - r * Math.sin(theta);
const color = value > MIN ? '#0A39DF' : '#CBD5E1';
return (
<div className="flex flex-col items-center shrink-0">
<span className="text-[8px] font-semibold text-[#8A94A4] tracking-[0.12em] mb-0.5">CAREER SCORE</span>
<svg width="110" height="64" viewBox="0 0 110 64" className="overflow-visible">
<path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`} fill="none" stroke="#E1E4E8" strokeWidth="8" strokeLinecap="round" />
{value > MIN && (
<>
<path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${xv} ${yv}`} fill="none" stroke={color} strokeWidth="8" strokeLinecap="round" />
<circle cx={xv} cy={yv} r="5.5" fill={color} stroke="#fff" strokeWidth="1.5" />
</>
)}
<text x={cx} y={cy - 6} textAnchor="middle" className="fill-[#0A39DF]" style={{ fontSize: 20, fontWeight: 700 }}>{value}</text>
<text x={cx} y={cy + 9} textAnchor="middle" fill="#8A94A4" style={{ fontSize: 8, fontWeight: 600 }}>300-850</text>
</svg>
</div>
);
}
const BADGE_LEVEL_STYLES = {
bronze: 'bg-[#FEF3C7] text-[#92400E]',
silver: 'bg-[#F3F4F6] text-[#4B5563]',
gold: 'bg-[#FEF9E7] text-[#92400E]',
platinum: 'bg-[#EFF6FF] text-[#1E40AF]',
};
export default function TalentPoolCard({ profile, onContact, onView }) {
const [expanded, setExpanded] = useState(false);
const score = profile.krow_score || 0;
const band = getScoreBand(score);
const fico = toFICO(score);
const hasScore = score > 0;
const skills = (profile.skills || []).slice(0, 4);
const extraSkills = (profile.skills || []).length - skills.length;
const badges = (profile.earned_badges || []).slice(0, 3);
const extraBadges = (profile.earned_badges || []).length - badges.length;
const capabilities = profile.capabilities || [];
const endorsements = capabilities.filter((c) => c.status === 'verified' || c.status === 'pending').slice(0, 3);
const extraEndorsements = capabilities.length - endorsements.length;
const toggle = (e) => {
e.stopPropagation();
setExpanded((v) => !v);
};
return (
<div
className={cn(
'bg-white border border-[#E5E7EB] rounded-xl transition-all overflow-hidden',
expanded ? 'shadow-sm border-[#0A39DF]' : 'hover:shadow-sm hover:border-[#0A39DF]'
)}
>
{/* Clickable header → opens full profile modal */}
<div
className="p-4 cursor-pointer"
onClick={() => onView?.(profile)}
>
<div className="flex items-start gap-4">
{/* Avatar */}
<div className="w-14 h-14 rounded-full bg-[#E5E7EB] overflow-hidden shrink-0">
<Image
src={profile.selfie_url || `https://images.unsplash.com/${getPortraitSeed(profile.full_name)}?w=140&h=140&fit=crop&crop=faces`}
alt={profile.full_name}
className="w-full h-full object-cover"
fittingType="fill"
/>
</div>
{/* Info */}
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<h3 className="font-semibold text-[#111827] text-[15px] truncate">{profile.full_name}</h3>
<span
className="px-2 py-0.5 rounded-full text-[10px] font-bold"
style={hasScore
? { backgroundColor: band.color, color: '#fff' }
: { backgroundColor: '#E5E7EB', color: '#6B7280' }}
>
{hasScore ? band.label : 'No score yet'}
</span>
</div>
</div>
{/* Career Score gauge */}
<CareerGauge fico={fico} />
</div>
</div>
{/* Expanded details */}
{expanded && (
<div className="px-4 pb-4 space-y-3">
{/* Quick meta line */}
<div className="flex items-center gap-3 text-[11px] text-[#6B7280] flex-wrap">
{profile.desired_position && (
<span className="inline-flex items-center gap-1 font-medium text-[#0A39DF]">
<Briefcase className="w-3 h-3" />
{profile.desired_position}
</span>
)}
{profile.experience_years > 0 && (
<span>{profile.experience_years}yr exp</span>
)}
{profile.location && (
<span className="inline-flex items-center gap-1">
<MapPin className="w-3 h-3" />
{profile.location}
</span>
)}
{(profile.client_rating > 0 || profile.supervisor_rating > 0) && (
<span className="inline-flex items-center gap-1 text-[#F59E0B] font-medium">
<Star className="w-3 h-3 fill-[#F59E0B]" />
{((profile.client_rating + profile.supervisor_rating) / 2).toFixed(1)}
</span>
)}
</div>
{skills.length > 0 && (
<div className="flex items-center gap-1.5 flex-wrap">
{skills.map((s) => (
<span key={s} className="px-2 py-0.5 rounded-full bg-[#EFF6FF] text-[#0A39DF] text-[10px] font-medium">{s}</span>
))}
{extraSkills > 0 && (
<span className="px-2 py-0.5 rounded-full bg-[#F3F4F6] text-[#6B7280] text-[10px] font-medium">+{extraSkills}</span>
)}
</div>
)}
{(profile.availability || []).length > 0 && (
<div className="flex items-center gap-1.5 flex-wrap">
{(profile.availability || []).slice(0, 3).map((a) => (
<span key={a} className="px-2 py-0.5 rounded-full bg-[#F3F4F6] text-[#374151] text-[10px]">{a}</span>
))}
</div>
)}
{/* Badges */}
{badges.length > 0 && (
<div className="pt-3 border-t border-[#F3F4F6]">
<p className="text-[10px] font-bold text-[#9CA3AF] tracking-wider mb-2 flex items-center gap-1.5">
<Award className="w-3 h-3" /> BADGES
</p>
<div className="flex items-center gap-1.5 flex-wrap">
{badges.map((b, i) => (
<span key={i} className={cn('inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-medium', BADGE_LEVEL_STYLES[b.level] || BADGE_LEVEL_STYLES.bronze)}>
<Award className="w-2.5 h-2.5" />
{b.name}
</span>
))}
{extraBadges > 0 && (
<span className="px-2 py-0.5 rounded-full bg-[#F3F4F6] text-[#6B7280] text-[10px] font-medium">+{extraBadges}</span>
)}
</div>
</div>
)}
{/* Endorsements */}
{endorsements.length > 0 && (
<div className="pt-3 border-t border-[#F3F4F6]">
<p className="text-[10px] font-bold text-[#9CA3AF] tracking-wider mb-2 flex items-center gap-1.5">
<BadgeCheck className="w-3 h-3" /> ENDORSEMENTS
<span className="text-[#D1D5DB] font-semibold">({capabilities.length})</span>
</p>
<div className="flex items-center gap-1.5 flex-wrap">
{endorsements.map((e, i) => (
<span key={i} className={cn('inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-medium border', e.status === 'verified' ? 'bg-[#EFF6FF] text-[#0A39DF] border-[#DBEAFE]' : 'bg-[#F3F4F6] text-[#6B7280] border-[#E5E7EB]')}>
<BadgeCheck className="w-2.5 h-2.5" />
{e.skill}
{e.level && <span className="opacity-70">· {e.level}</span>}
</span>
))}
{extraEndorsements > 0 && (
<span className="px-2 py-0.5 rounded-full bg-[#F3F4F6] text-[#6B7280] text-[10px] font-medium">+{extraEndorsements}</span>
)}
</div>
</div>
)}
{/* Employer insights */}
<div className="pt-3 border-t border-[#F3F4F6]">
<EmployerInsights profile={profile} />
</div>
</div>
)}
{/* Footer: email + contact + expand toggle */}
<div className="flex items-center gap-2 px-4 py-3 border-t border-[#F3F4F6]">
<span className="text-[11px] text-[#9CA3AF] flex-1 truncate">{profile.email || 'No email'}</span>
{onContact && (
<button
onClick={(e) => { e.stopPropagation(); onContact(profile); }}
className="inline-flex items-center gap-1.5 bg-[#0A39DF] hover:bg-[#082EB4] text-white text-[12px] font-medium px-3 py-1.5 rounded-lg transition-colors whitespace-nowrap"
>
<Mail className="w-3.5 h-3.5" />
Contact
</button>
)}
<button
onClick={toggle}
className="inline-flex items-center gap-1 border border-[#E5E7EB] hover:bg-[#F9FAFB] text-[#374151] text-[12px] font-medium px-2.5 py-1.5 rounded-lg transition-colors"
aria-expanded={expanded}
>
<ChevronDown className={cn('w-3.5 h-3.5 transition-transform', expanded && 'rotate-180')} />
{expanded ? 'Hide' : 'Details'}
</button>
</div>
</div>
);
}