"use client"; import React, { useState, useEffect } from "react"; interface Score { id: string; candidate_id: string; ai_score: number; evaluation: { summary: string; classification: string; suggestions: string; riskLevel: string; }; created_at: string; } interface Candidate { id: string; name: string; contact_info: { email: string; phone: string; skills?: string[]; summary?: string; }; scores?: Score[]; created_at: string; } interface UploadFileStatus { name: string; status: "uploading" | "success" | "error"; errorMessage?: string; } export default function CandidatesPage() { const [candidates, setCandidates] = useState([]); const [loading, setLoading] = useState(true); const [uploading, setUploading] = useState(false); const [uploadError, setUploadError] = useState(null); const [uploadSuccess, setUploadSuccess] = useState(null); const [uploadStatuses, setUploadStatuses] = useState([]); const fetchCandidates = () => { fetch("/api/candidates") .then((res) => { if (!res.ok) throw new Error("Failed to fetch candidates"); return res.json(); }) .then((data) => { setCandidates(data); setLoading(false); }) .catch((err) => { console.error(err); setLoading(false); }); }; useEffect(() => { fetchCandidates(); }, []); // Upload PDF CVs in a vacuum const handleFileUpload = async (e: React.ChangeEvent) => { const files = e.target.files; if (!files || files.length === 0) return; const fileList = Array.from(files); // Set initial status const initialStatuses = fileList.map((file) => ({ name: file.name, status: "uploading" as const, })); setUploadStatuses(initialStatuses); setUploading(true); setUploadError(null); setUploadSuccess(null); const uploadPromises = fileList.map(async (file, index) => { if (file.type !== "application/pdf") { setUploadStatuses((prev) => prev.map((status, idx) => idx === index ? { ...status, status: "error" as const, errorMessage: "Only PDF files are allowed" } : status ) ); return; } try { const formData = new FormData(); formData.append("file", file); const res = await fetch("/candidates/api/parse-cv", { method: "POST", body: formData, }); if (!res.ok) { const errData = await res.json(); throw new Error(errData.error || "Failed to process CV"); } await res.json(); setUploadStatuses((prev) => prev.map((status, idx) => idx === index ? { ...status, status: "success" as const } : status ) ); } catch (err: unknown) { const msg = err instanceof Error ? err.message : "Error uploading CV"; setUploadStatuses((prev) => prev.map((status, idx) => idx === index ? { ...status, status: "error" as const, errorMessage: msg } : status ) ); } }); await Promise.all(uploadPromises); setUploading(false); fetchCandidates(); e.target.value = ""; }; return (

Candidates

A list of all candidates parsed and analyzed by the AI recruitment pipeline.

{/* CV Uploader (Vacuum Ingestion) */}

Ingest Candidate CV (in a Vacuum)

Upload a candidate CV PDF to parse contact info, skills, and summary. No job position will be associated initially, keeping the data isolated.

{uploadError && (

{uploadError}

)} {uploadSuccess && (

{uploadSuccess}

)} {uploadStatuses.length > 0 && (

Upload Progress

    {uploadStatuses.map((item, idx) => (
  • {item.name} {item.status === "uploading" && ( Uploading... )} {item.status === "success" && ( ✓ Success )} {item.status === "error" && ( ✗ Error )}
    {item.errorMessage && (

    {item.errorMessage}

    )}
  • ))}
)}
{loading ? (

Loading candidates...

) : candidates.length === 0 ? (

No candidates found. Upload a CV above to get started.

) : (
{candidates.map((candidate) => { return (
{/* Candidate Info Header */}

{candidate.name}

Email:{" "} {candidate.contact_info.email} Phone:{" "} {candidate.contact_info.phone}
{/* Extracted Profile (Summary & Skills) */}
{candidate.contact_info.summary && (
Professional Summary (Extracted)

{candidate.contact_info.summary}

)} {candidate.contact_info.skills && candidate.contact_info.skills.length > 0 && (
Skills & Technologies
{candidate.contact_info.skills.map((skill) => ( {skill} ))}
)}
); })}
)}
); }