Coderound / proj /frontend /src /app /page.jsx
cloud450's picture
Upload 42 files
ab13a8a verified
'use client';
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Upload, Search, Zap, CheckCircle2, AlertCircle, Loader2, BarChart3, Users } from 'lucide-react';
import { uploadCSV, runEvaluation } from '@/lib/api';
import CandidateTable from '@/components/CandidateTable';
import CandidateDetail from '@/components/CandidateDetail';
export default function Dashboard() {
const [jd, setJd] = useState('');
const [candidates, setCandidates] = useState([]);
const [evaluations, setEvaluations] = useState([]);
const [isUploading, setIsUploading] = useState(false);
const [isEvaluating, setIsEvaluating] = useState(false);
const [selectedCandidate, setSelectedCandidate] = useState(null);
const handleFileUpload = async (e) => {
const file = e.target.files[0];
if (!file) return;
setIsUploading(true);
try {
const data = await uploadCSV(file);
setCandidates(data.candidates);
} catch (err) {
console.error(err);
alert('Failed to upload CSV. Check console for details.');
} finally {
setIsUploading(false);
}
};
const handleEvaluate = async () => {
if (!jd || candidates.length === 0) {
alert('Please provide a JD and upload candidates.');
return;
}
setIsEvaluating(true);
try {
const data = await runEvaluation(jd, candidates);
setEvaluations(data.shortlist);
// We can also store data.details if needed for immediate access
window.evalDetails = data.details;
} catch (err) {
console.error(err);
alert('Evaluation failed. Check backend/Groq keys.');
} finally {
setIsEvaluating(false);
}
};
return (
<div className="container mx-auto px-6 py-12">
<header className="mb-12">
<motion.h2
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
className="text-4xl font-extrabold tracking-tight"
>
Candidate <span className="gradient-text">Ranking Platform</span>
</motion.h2>
<p className="text-slate-500 mt-2 text-lg">Intelligent multi-agent assessment for your next top hire.</p>
</header>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8">
{/* Left Column: Input */}
<div className="lg:col-span-4 space-y-6">
<section className="glass-card p-6 rounded-2xl">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<Zap className="w-5 h-5 text-indigo-500" /> Job Description
</h3>
<textarea
className="w-full h-64 p-4 bg-white/50 dark:bg-slate-900/50 border border-slate-200 dark:border-slate-800 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all resize-none text-sm"
placeholder="Paste the job description here..."
value={jd}
onChange={(e) => setJd(e.target.value)}
/>
</section>
<section className="glass-card p-6 rounded-2xl">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2 text-indigo-500">
<Upload className="w-5 h-5" /> Candidate Data (CSV)
</h3>
<div className="relative group">
<input
type="file"
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer z-10"
onChange={handleFileUpload}
accept=".csv"
/>
<div className="border-2 border-dashed border-slate-300 dark:border-slate-700 rounded-xl p-8 text-center group-hover:border-indigo-500 transition-colors">
{isUploading ? (
<Loader2 className="w-8 h-8 mx-auto animate-spin text-indigo-500" />
) : (
<>
<Users className="w-8 h-8 mx-auto text-slate-400 group-hover:text-indigo-500 transition-colors mb-2" />
<p className="text-sm font-medium">Click or drag CSV</p>
<p className="text-xs text-slate-500 mt-1">{candidates.length > 0 ? `${candidates.length} candidates loaded` : 'Supports names, skills, email...'}</p>
</>
)}
</div>
</div>
</section>
<button
onClick={handleEvaluate}
disabled={isEvaluating || !jd || candidates.length === 0}
className={`w-full py-4 rounded-xl flex items-center justify-center gap-2 font-bold text-white transition-all shadow-lg ${
isEvaluating || !jd || candidates.length === 0
? 'bg-slate-400 cursor-not-allowed'
: 'bg-indigo-600 hover:bg-indigo-700 hover:scale-[1.02] active:scale-[0.98]'
}`}
>
{isEvaluating ? (
<Loader2 className="w-5 h-5 animate-spin" />
) : (
<BarChart3 className="w-5 h-5" />
)}
{isEvaluating ? 'Running AI Agents...' : 'Run Evaluation'}
</button>
</div>
{/* Right Column: Results */}
<div className="lg:col-span-8">
<div className="glass-card rounded-2xl min-h-[600px] relative overflow-hidden">
<AnimatePresence mode="wait">
{evaluations.length > 0 ? (
<motion.div
key="results"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="p-6"
>
<CandidateTable
evaluations={evaluations}
onViewDetail={(c) => setSelectedCandidate(c)}
/>
</motion.div>
) : (
<motion.div
key="empty"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute inset-0 flex flex-col items-center justify-center text-slate-400 p-12 text-center"
>
<Search className="w-16 h-16 mb-4 opacity-20" />
<h3 className="text-xl font-medium">No Evaluations Yet</h3>
<p className="max-w-xs mt-2 text-sm">Upload a CSV, paste a JD, and hit Run to see the magic happen with Groq LLMs.</p>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</div>
{/* Detail Modal */}
<AnimatePresence>
{selectedCandidate && (
<CandidateDetail
evaluation={selectedCandidate}
onClose={() => setSelectedCandidate(null)}
/>
)}
</AnimatePresence>
</div>
);
}