import React, { useState, useEffect, useCallback, useRef } from 'react';
import {
Box,
Stepper,
Step,
StepLabel,
Button,
Typography,
Paper,
useTheme,
Snackbar,
Alert,
Tabs,
Tab,
Avatar,
useMediaQuery,
Divider,
} from '@mui/material';
import { Add, WorkOutline } from '@mui/icons-material';
import PersonIcon from '@mui/icons-material/Person';
import WorkIcon from '@mui/icons-material/Work';
import AssessmentIcon from '@mui/icons-material/Assessment';
import { JobMatchAnalysis } from 'components/JobMatchAnalysis';
import { Candidate, Job, SkillAssessment } from 'types/types';
import { useNavigate } from 'react-router-dom';
import { BackstoryPageProps } from 'components/BackstoryTab';
import { useAuth } from 'hooks/AuthContext';
import { useAppState, useSelectedCandidate, useSelectedJob } from 'hooks/GlobalContext';
import { CandidateInfo } from 'components/ui/CandidateInfo';
import { ComingSoon } from 'components/ui/ComingSoon';
import { LoginRequired } from 'components/ui/LoginRequired';
import { Scrollable } from 'components/Scrollable';
import { CandidatePicker } from 'components/ui/CandidatePicker';
import { JobPicker } from 'components/ui/JobPicker';
import { JobCreator } from 'components/JobCreator';
import { LoginRestricted } from 'components/ui/LoginRestricted';
import JsonView from '@uiw/react-json-view';
import { ResumeGenerator } from 'components/ResumeGenerator';
import { JobInfo } from 'components/ui/JobInfo';
function WorkAddIcon() {
return (
);
}
interface AnalysisState {
job: Job | null;
candidate: Candidate | null;
analysis: SkillAssessment[] | null;
resume: string | null;
}
interface Step {
index: number;
label: string;
requiredState: string[];
title: string;
icon: React.ReactNode;
}
const initialState: AnalysisState = {
job: null,
candidate: null,
analysis: null,
resume: null,
};
// Steps in our process
const steps: Step[] = [
{ requiredState: [], title: 'Job Selection', icon: },
{ requiredState: ['job'], title: 'Select Candidate', icon: },
{
requiredState: ['job', 'candidate'],
title: 'Job Analysis',
icon: ,
},
{
requiredState: ['job', 'candidate', 'analysis'],
title: 'Generated Resume',
icon: ,
},
].map((item, index) => {
return { ...item, index, label: item.title.toLowerCase().replace(/ /g, '-') };
});
const capitalize = (str: string) => {
return str.charAt(0).toUpperCase() + str.slice(1);
};
// Main component
const JobAnalysisPage: React.FC = (props: BackstoryPageProps) => {
const theme = useTheme();
const { user, guest } = useAuth();
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
const { selectedJob, setSelectedJob } = useSelectedJob();
const [activeStep, setActiveStep] = useState(steps[0]);
const [error, setError] = useState(null);
const [jobTab, setJobTab] = useState('select');
const [analysisState, setAnalysisState] = useState(null);
const [canAdvance, setCanAdvance] = useState(false);
const scrollRef = useRef(null);
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const canAccessStep = useCallback(
(step: Step) => {
if (!analysisState) {
return;
}
const missing = step.requiredState.find(f => !(analysisState as any)[f]);
return missing;
},
[analysisState]
);
useEffect(() => {
if (analysisState !== null) {
return;
}
const analysis = {
...initialState,
candidate: selectedCandidate,
job: selectedJob,
};
setAnalysisState(analysis);
for (let i = steps.length - 1; i >= 0; i--) {
const missing = steps[i].requiredState.find(f => !(analysis as any)[f]);
if (!missing) {
setActiveStep(steps[i]);
return;
}
}
}, [analysisState, selectedCandidate, selectedJob, setActiveStep, canAccessStep]);
useEffect(() => {
if (activeStep.index === steps.length - 1) {
setCanAdvance(false);
return;
}
const blocked = canAccessStep(steps[activeStep.index + 1]);
if (blocked) {
setCanAdvance(false);
} else {
setCanAdvance(true);
}
if (scrollRef.current) {
scrollRef.current.scrollTo({
top: 0,
behavior: 'smooth',
});
}
}, [setCanAdvance, analysisState, activeStep]);
const handleNext = () => {
if (activeStep.index === steps.length - 1) {
return;
}
const missing = canAccessStep(steps[activeStep.index + 1]);
if (missing) {
setError(`${capitalize(missing)} is necessary before continuing.`);
return missing;
}
if (activeStep.index < steps.length - 1) {
setActiveStep(prevActiveStep => steps[prevActiveStep.index + 1]);
}
};
const handleBack = () => {
if (activeStep.index === 0) {
return;
}
setActiveStep(prevActiveStep => steps[prevActiveStep.index - 1]);
};
const moveToStep = (step: number) => {
const missing = canAccessStep(steps[step]);
if (missing) {
setError(`${capitalize(missing)} is needed to access this step.`);
return;
}
setActiveStep(steps[step]);
};
const onCandidateSelect = (candidate: Candidate) => {
if (!analysisState) {
return;
}
analysisState.candidate = candidate;
setAnalysisState({ ...analysisState });
setSelectedCandidate(candidate);
handleNext();
};
const onJobSelect = (job: Job) => {
if (!analysisState) {
return;
}
analysisState.job = job;
setAnalysisState({ ...analysisState });
setSelectedJob(job);
handleNext();
};
// Render function for the candidate selection step
const renderCandidateSelection = () => (
);
const handleTabChange = (event: React.SyntheticEvent, value: string) => {
setJobTab(value);
};
// Render function for the job description step
const renderJobDescription = () => {
return (
} label="Select Job" />
} label="Create Job" />
{jobTab === 'select' && }
{jobTab === 'create' && user && }
{jobTab === 'create' && guest && (
)}
);
};
const onAnalysisComplete = (skills: SkillAssessment[]) => {
if (!analysisState) {
return;
}
analysisState.analysis = skills;
setAnalysisState({ ...analysisState });
};
// Render function for the analysis step
const renderAnalysis = () => {
if (!analysisState) {
return;
}
if (!analysisState.job || !analysisState.candidate) {
return (
{JSON.stringify({
job: analysisState.job,
candidate: analysisState.candidate,
})}
);
}
return (
);
};
const renderResume = () => {
if (!analysisState) {
return;
}
if (!analysisState.job || !analysisState.candidate || !analysisState.analysis) {
return <>>;
}
return (
);
};
return (
*:not(.Scrollable)': {
flexShrink: 0 /* Prevent shrinking */,
},
position: 'relative',
}}
>
{steps.map((step, index) => (
{
moveToStep(index);
}}
slots={{
stepIcon: () => (
= step.index
? theme.palette.primary.main
: theme.palette.grey[300],
color: 'white',
}}
>
{step.icon}
),
}}
>
{step.title}
))}
{analysisState && analysisState.job && (
{!isMobile && (
)}
)}
{isMobile && }
{!isMobile && }
{analysisState && analysisState.candidate && (
)}
{activeStep.label === 'job-selection' && renderJobDescription()}
{activeStep.label === 'select-candidate' && renderCandidateSelection()}
{activeStep.label === 'job-analysis' && renderAnalysis()}
{activeStep.label === 'generated-resume' && renderResume()}
{activeStep.index === steps[steps.length - 1].index ? (
) : (
)}
{/* Error Snackbar */}
setError(null)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
setError(null)} severity="error" sx={{ width: '100%' }}>
{error}
);
};
export { JobAnalysisPage };