import React, { useEffect, useState, useMemo } from 'react'; import { supabase } from '../../supabaseClient'; import { motion, AnimatePresence } from 'framer-motion'; export default function TopPerformers() { // --- STATE --- const [candidates, setCandidates] = useState([]); const [loading, setLoading] = useState(true); const [showConfig, setShowConfig] = useState(false); // Config State const [config, setConfig] = useState({ skillsWeight: 5, experienceWeight: 5, projectWeight: 5, }); const handleConfigChange = (key, e) => { setConfig({ ...config, [key]: parseInt(e.target.value) }); }; // --- SUPABASE DATA FETCHING --- useEffect(() => { const fetchCandidates = async () => { try { // ✅ FIX: Removed comments inside the string const { data, error } = await supabase .from('applications') .select(` id, match_score, experience, profiles ( id, full_name, avatar_url, technical_skills, projects, experience_years, summary ), jobs ( title ) `) .limit(10); if (error) throw error; setCandidates(data || []); } catch (error) { console.error("Fetch error:", error); } finally { setLoading(false); } }; fetchCandidates(); }, []); // --- ⚡️ LOGIC ENGINE --- const processedCandidates = useMemo(() => { if (!candidates.length) return []; return candidates.map(candidate => { const profile = candidate.profiles || {}; const job = candidate.jobs || {}; // 1. EXPERIENCE - Try multiple sources const expVal = parseFloat(candidate.experience) || parseFloat(profile.experience_years) || 0; const expScore = Math.min(expVal * 10, 100); // 2. SKILLS - Use match_score with fallback to skill count let skillScore = parseFloat(candidate.match_score) || 0; if (skillScore === 0) { // Fallback: count technical skills const skillsText = profile.technical_skills || ""; const skillCount = typeof skillsText === 'string' && skillsText.trim().length > 0 ? skillsText.split(',').length : 0; skillScore = Math.min(skillCount * 10, 100); // Each skill = 10 points } // Display Logic Only const skillsText = profile.technical_skills || ""; const skillCountForDisplay = typeof skillsText === 'string' && skillsText.trim().length > 0 ? skillsText.split(',').length : 0; // 3. PROJECTS (JSON Array -> Number) const projectsData = profile.projects; const projCount = Array.isArray(projectsData) ? projectsData.length : 0; const projScore = Math.min(projCount * 20, 100); // 4. WEIGHTED FORMULA - BOOST MODEL // ✅ FIXED: Each weight acts as a boost multiplier // Weight of 5 = 1x, weight of 10 = 2x amplification // This ensures increasing a weight ALWAYS increases the score const skillBoost = skillScore * (1 + config.skillsWeight / 10); const expBoost = expScore * (1 + config.experienceWeight / 10); const projBoost = projScore * (1 + config.projectWeight / 10); const rawScore = Math.min((skillBoost + expBoost + projBoost) / 3, 100); return { ...candidate, id: candidate.id, name: profile.full_name || 'Candidate', avatar: profile.avatar_url, role: job.title || 'Applicant', summary: profile.summary, finalScore: Math.round(rawScore), displayExp: expVal, displayProj: projCount, displaySkills: skillCountForDisplay }; }) .sort((a, b) => b.finalScore - a.finalScore) .slice(0, 5); }, [candidates, config]); // --- STYLES --- const containerStyle = { backgroundColor: 'rgba(239, 68, 68, 0.05)', border: '1px solid rgba(239, 68, 68, 0.2)', borderRadius: '1rem', padding: '1.5rem', color: 'white', height: '100%', fontFamily: 'sans-serif' }; const configBoxStyle = { backgroundColor: 'rgba(0, 0, 0, 0.3)', border: '1px solid rgba(255, 255, 255, 0.1)', borderRadius: '0.75rem', padding: '1.25rem', marginBottom: '1.5rem', marginTop: '0.5rem' }; const labelRowStyle = { display: 'flex', justifyContent: 'space-between', marginBottom: '0.5rem', fontSize: '0.85rem', fontWeight: '500', color: '#D1D5DB' }; const getSliderStyle = (value, max) => { const percentage = (value / max) * 100; return { width: '100%', height: '6px', borderRadius: '3px', background: `linear-gradient(to right, #EF4444 0%, #EF4444 ${percentage}%, #374151 ${percentage}%, #374151 100%)`, appearance: 'none', outline: 'none', cursor: 'pointer' }; }; return (
Calculated based on Match Score, Experience & Projects
{/* CONFIG PANEL */}Loading...
) : processedCandidates.length > 0 ? ( processedCandidates.map((item) => { const exp = item.displayExp > 0 ? `${item.displayExp} yrs` : 'Fresher'; return (No candidates found
)}