import React, { useState, useEffect } from 'react'; import { Box, Typography, Paper, Accordion, AccordionSummary, AccordionDetails, CircularProgress, Grid, Chip, Divider, Card, CardContent, useTheme, LinearProgress } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import CheckCircleIcon from '@mui/icons-material/CheckCircle'; import ErrorIcon from '@mui/icons-material/Error'; import PendingIcon from '@mui/icons-material/Pending'; import WarningIcon from '@mui/icons-material/Warning'; // Define TypeScript interfaces for our data structures interface Citation { text: string; source: string; relevance: number; // 0-100 scale } interface SkillMatch { requirement: string; status: 'pending' | 'complete' | 'error'; matchScore: number; // 0-100 scale assessment: string; citations: Citation[]; } interface JobAnalysisProps { jobTitle: string; candidateName: string; // This function would connect to your backend and return updates fetchRequirements: () => Promise; // This function would fetch match data for a specific requirement fetchMatchForRequirement: (requirement: string) => Promise; } const JobMatchAnalysis: React.FC = ({ jobTitle, candidateName, fetchRequirements, fetchMatchForRequirement }) => { const theme = useTheme(); const [requirements, setRequirements] = useState([]); const [skillMatches, setSkillMatches] = useState([]); const [loadingRequirements, setLoadingRequirements] = useState(true); const [expanded, setExpanded] = useState(false); const [overallScore, setOverallScore] = useState(0); // Handle accordion expansion const handleAccordionChange = (panel: string) => (event: React.SyntheticEvent, isExpanded: boolean) => { setExpanded(isExpanded ? panel : false); }; // Fetch initial requirements useEffect(() => { const getRequirements = async () => { try { const fetchedRequirements = await fetchRequirements(); setRequirements(fetchedRequirements); // Initialize skill matches with pending status const initialSkillMatches = fetchedRequirements.map(req => ({ requirement: req, status: 'pending' as const, matchScore: 0, assessment: '', citations: [] })); setSkillMatches(initialSkillMatches); setLoadingRequirements(false); } catch (error) { console.error("Error fetching requirements:", error); setLoadingRequirements(false); } }; getRequirements(); }, [fetchRequirements]); // Fetch match data for each requirement useEffect(() => { const fetchMatchData = async () => { if (requirements.length === 0) return; // Process requirements one by one for (let i = 0; i < requirements.length; i++) { try { const match = await fetchMatchForRequirement(requirements[i]); setSkillMatches(prev => { const updated = [...prev]; updated[i] = match; return updated; }); // Update overall score setSkillMatches(current => { const completedMatches = current.filter(match => match.status === 'complete'); if (completedMatches.length > 0) { const newOverallScore = completedMatches.reduce((sum, match) => sum + match.matchScore, 0) / completedMatches.length; setOverallScore(newOverallScore); } return current; }); } catch (error) { console.error(`Error fetching match for requirement ${requirements[i]}:`, error); setSkillMatches(prev => { const updated = [...prev]; updated[i] = { ...updated[i], status: 'error', assessment: 'Failed to analyze this requirement.' }; return updated; }); } } }; if (!loadingRequirements) { fetchMatchData(); } }, [requirements, loadingRequirements, fetchMatchForRequirement]); // Get color based on match score const getMatchColor = (score: number): string => { if (score >= 80) return theme.palette.success.main; if (score >= 60) return theme.palette.info.main; if (score >= 40) return theme.palette.warning.main; return theme.palette.error.main; }; // Get icon based on status const getStatusIcon = (status: string, score: number) => { if (status === 'pending') return ; if (status === 'error') return ; if (score >= 70) return ; if (score >= 40) return ; return ; }; return ( Job Match Analysis Job: {jobTitle} Candidate: {candidateName} Overall Match: {`${Math.round(overallScore)}%`} = 80 ? "Excellent Match" : overallScore >= 60 ? "Good Match" : overallScore >= 40 ? "Partial Match" : "Low Match" } sx={{ bgcolor: getMatchColor(overallScore), color: 'white', fontWeight: 'bold' }} /> {loadingRequirements ? ( Analyzing job requirements... ) : ( Requirements Analysis {skillMatches.map((match, index) => ( } aria-controls={`panel${index}bh-content`} id={`panel${index}bh-header`} sx={{ bgcolor: match.status === 'complete' ? `${getMatchColor(match.matchScore)}22` // Add transparency : 'inherit' }} > {getStatusIcon(match.status, match.matchScore)} {match.requirement} {match.status === 'complete' ? ( ) : match.status === 'pending' ? ( ) : ( )} {match.status === 'pending' ? ( Analyzing candidate's match for this requirement... ) : match.status === 'error' ? ( {match.assessment || "An error occurred while analyzing this requirement."} ) : ( Assessment: {match.assessment} Supporting Evidence: {match.citations.length > 0 ? ( match.citations.map((citation, citIndex) => ( "{citation.text}" Source: {citation.source} )) ) : ( No specific evidence found in candidate's profile. )} )} ))} )} ); }; export { JobMatchAnalysis };