Compare commits
No commits in common. "5fed56ba76e3a26262eb1477434bbacfd0b51403" and "b76141f3d12595657024fab217eb2587e69b9c98" have entirely different histories.
5fed56ba76
...
b76141f3d1
@ -354,20 +354,13 @@ const JobManagement = (props: BackstoryElementProps) => {
|
|||||||
// This would call your API to extract requirements from the job description
|
// This would call your API to extract requirements from the job description
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadJob = async () => {
|
const renderJobCreation = () => {
|
||||||
const job = await apiClient.getJob("7594e989-a926-45a2-9b07-ae553d2e0d0d");
|
if (!user) {
|
||||||
setSelectedJob(job);
|
return <Box>You must be logged in</Box>;
|
||||||
}
|
}
|
||||||
const renderJobCreation = () => {
|
|
||||||
if (!user) {
|
|
||||||
return <Box>You must be logged in</Box>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box sx={{ maxWidth: 1200, mx: 'auto', p: { xs: 2, sm: 3 } }}>
|
||||||
mx: 'auto', p: { xs: 2, sm: 3 },
|
|
||||||
}}>
|
|
||||||
<Button onClick={loadJob} variant="contained">Load Job</Button>
|
|
||||||
{/* Upload Section */}
|
{/* Upload Section */}
|
||||||
<Card elevation={3} sx={{ mb: 4 }}>
|
<Card elevation={3} sx={{ mb: 4 }}>
|
||||||
<CardHeader
|
<CardHeader
|
||||||
@ -544,10 +537,10 @@ const JobManagement = (props: BackstoryElementProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className="JobManagement"
|
<Box sx={{
|
||||||
sx={{
|
minHeight: '100vh',
|
||||||
background: "white",
|
backgroundColor: 'background.default',
|
||||||
p: 0,
|
pt: { xs: 2, sm: 3 }
|
||||||
}}>
|
}}>
|
||||||
{selectedJob === null && renderJobCreation()}
|
{selectedJob === null && renderJobCreation()}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -322,7 +322,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="h6" component="h2">
|
<Typography variant="h6" component="h2">
|
||||||
Backstory Generated Job Summary:
|
Backstory Job Summary:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" component="h2">
|
<Typography variant="body1" component="h2">
|
||||||
{job.summary || "N/A"}
|
{job.summary || "N/A"}
|
||||||
@ -333,7 +333,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
|||||||
|
|
||||||
<Grid size={{ xs: 12, md: 6 }} sx={{ display: "flex", flexShrink: 1, flexDirection: "column" }}>
|
<Grid size={{ xs: 12, md: 6 }} sx={{ display: "flex", flexShrink: 1, flexDirection: "column" }}>
|
||||||
<Typography variant="h6" component="h2">
|
<Typography variant="h6" component="h2">
|
||||||
Original Job Description:
|
Job Description:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Paper sx={{ p: 2, maxHeight: "22rem" }}>
|
<Paper sx={{ p: 2, maxHeight: "22rem" }}>
|
||||||
<Scrollable sx={{ display: "flex", maxHeight: "100%" }}>
|
<Scrollable sx={{ display: "flex", maxHeight: "100%" }}>
|
||||||
|
@ -98,34 +98,26 @@ const BackstoryPageContainer = (props : BackstoryPageContainerProps) => {
|
|||||||
className="BackstoryPageContainer"
|
className="BackstoryPageContainer"
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "row", // Must be row; if column, the box will expand for all children
|
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
p: "0 !important", // Let the first box use padding to offset main content
|
p: { xs: 0, sm: 0.5 }, // Zero padding on mobile (xs), 0.5 on larger screens (sm and up)
|
||||||
m: "0 auto !important",
|
m: "0 auto !important",
|
||||||
maxWidth: '1024px', //{ xs: '100%', md: '700px', lg: '1024px' },
|
maxWidth: '1024px', //{ xs: '100%', md: '700px', lg: '1024px' },
|
||||||
height: "100%", // Restrict to main-container's height
|
|
||||||
minHeight: 0,//"min-content", // Prevent flex overflow
|
|
||||||
...sx
|
...sx
|
||||||
}}>
|
|
||||||
<Box sx={{
|
|
||||||
display: "flex", p: { xs: 0, sm: 0.5 }, flexGrow: 1, minHeight: "min-content", // Prevent flex overflow
|
|
||||||
}}>
|
}}>
|
||||||
<Paper
|
<Paper
|
||||||
elevation={2}
|
elevation={2}
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
m: 0,
|
|
||||||
p: 0.5,
|
p: 0.5,
|
||||||
minHeight: "min-content", // Prevent flex overflow
|
|
||||||
backgroundColor: 'background.paper',
|
backgroundColor: 'background.paper',
|
||||||
borderRadius: 0.5,
|
borderRadius: 0.5,
|
||||||
|
minHeight: '80vh',
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,84 +0,0 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import Button from '@mui/material/Button';
|
|
||||||
import Box from '@mui/material/Box';
|
|
||||||
|
|
||||||
import { BackstoryElementProps } from 'components/BackstoryTab';
|
|
||||||
import { CandidateInfo } from 'components/CandidateInfo';
|
|
||||||
import { Candidate } from "types/types";
|
|
||||||
import { useAuth } from 'hooks/AuthContext';
|
|
||||||
import { useSelectedCandidate } from 'hooks/GlobalContext';
|
|
||||||
|
|
||||||
interface CandidatePickerProps extends BackstoryElementProps {
|
|
||||||
onSelect?: (candidate: Candidate) => void
|
|
||||||
};
|
|
||||||
|
|
||||||
const CandidatePicker = (props: CandidatePickerProps) => {
|
|
||||||
const { onSelect } = props;
|
|
||||||
const { apiClient, user } = useAuth();
|
|
||||||
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { setSnack } = props;
|
|
||||||
const [candidates, setCandidates] = useState<Candidate[] | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (candidates !== null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const getCandidates = async () => {
|
|
||||||
try {
|
|
||||||
const results = await apiClient.getCandidates();
|
|
||||||
const candidates: Candidate[] = results.data;
|
|
||||||
candidates.sort((a, b) => {
|
|
||||||
let result = a.lastName.localeCompare(b.lastName);
|
|
||||||
if (result === 0) {
|
|
||||||
result = a.firstName.localeCompare(b.firstName);
|
|
||||||
}
|
|
||||||
if (result === 0) {
|
|
||||||
result = a.username.localeCompare(b.username);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
setCandidates(candidates);
|
|
||||||
} catch (err) {
|
|
||||||
setSnack("" + err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
getCandidates();
|
|
||||||
}, [candidates, setSnack]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{display: "flex", flexDirection: "column"}}>
|
|
||||||
{user?.isAdmin &&
|
|
||||||
<Box sx={{ p: 1, textAlign: "center" }}>
|
|
||||||
Not seeing a candidate you like?
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
sx={{ m: 1 }}
|
|
||||||
onClick={() => { navigate('/generate-candidate') }}>
|
|
||||||
Generate your own perfect AI candidate!
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap", justifyContent: "center" }}>
|
|
||||||
{candidates?.map((u, i) =>
|
|
||||||
<Box key={`${u.username}`}
|
|
||||||
onClick={() => { onSelect ? onSelect(u) : setSelectedCandidate(u); }}
|
|
||||||
sx={{ cursor: "pointer" }}>
|
|
||||||
{selectedCandidate?.id === u.id &&
|
|
||||||
<CandidateInfo sx={{ maxWidth: "320px", "cursor": "pointer", backgroundColor: "#f0f0f0", "&:hover": { border: "2px solid orange" } }} candidate={u} />
|
|
||||||
}
|
|
||||||
{selectedCandidate?.id !== u.id &&
|
|
||||||
<CandidateInfo sx={{ maxWidth: "320px", "cursor": "pointer", border: "2px solid transparent", "&:hover": { border: "2px solid orange" } }} candidate={u} />
|
|
||||||
}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
CandidatePicker
|
|
||||||
};
|
|
@ -1,10 +1,77 @@
|
|||||||
import React, { } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
import { BackstoryPageProps } from '../components/BackstoryTab';
|
import { BackstoryPageProps } from '../components/BackstoryTab';
|
||||||
import { CandidatePicker } from 'components/ui/CandidatePicker';
|
import { CandidateInfo } from 'components/CandidateInfo';
|
||||||
|
import { Candidate, CandidateAI } from "../types/types";
|
||||||
|
import { useAuth } from 'hooks/AuthContext';
|
||||||
|
import { useSelectedCandidate } from 'hooks/GlobalContext';
|
||||||
|
|
||||||
const CandidateListingPage = (props: BackstoryPageProps) => {
|
const CandidateListingPage = (props: BackstoryPageProps) => {
|
||||||
return <CandidatePicker {...props} />;
|
const { apiClient, user } = useAuth();
|
||||||
|
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { setSnack } = props;
|
||||||
|
const [candidates, setCandidates] = useState<Candidate[] | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (candidates !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const getCandidates = async () => {
|
||||||
|
try {
|
||||||
|
const results = await apiClient.getCandidates();
|
||||||
|
const candidates: Candidate[] = results.data;
|
||||||
|
candidates.sort((a, b) => {
|
||||||
|
let result = a.lastName.localeCompare(b.lastName);
|
||||||
|
if (result === 0) {
|
||||||
|
result = a.firstName.localeCompare(b.firstName);
|
||||||
|
}
|
||||||
|
if (result === 0) {
|
||||||
|
result = a.username.localeCompare(b.username);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
setCandidates(candidates);
|
||||||
|
} catch (err) {
|
||||||
|
setSnack("" + err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getCandidates();
|
||||||
|
}, [candidates, setSnack]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{display: "flex", flexDirection: "column"}}>
|
||||||
|
{user?.isAdmin &&
|
||||||
|
<Box sx={{ p: 1, textAlign: "center" }}>
|
||||||
|
Not seeing a candidate you like?
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
sx={{ m: 1 }}
|
||||||
|
onClick={() => { navigate('/generate-candidate') }}>
|
||||||
|
Generate your own perfect AI candidate!
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
<Box sx={{ display: "flex", gap: 1, flexWrap: "wrap", justifyContent: "center" }}>
|
||||||
|
{candidates?.map((u, i) =>
|
||||||
|
<Box key={`${u.username}`}
|
||||||
|
onClick={() => { setSelectedCandidate(u); navigate("/chat"); }}
|
||||||
|
sx={{ cursor: "pointer" }}>
|
||||||
|
{selectedCandidate?.id === u.id &&
|
||||||
|
<CandidateInfo sx={{ maxWidth: "320px", "cursor": "pointer", backgroundColor: "#f0f0f0", "&:hover": { border: "2px solid orange" } }} candidate={u} />
|
||||||
|
}
|
||||||
|
{selectedCandidate?.id !== u.id &&
|
||||||
|
<CandidateInfo sx={{ maxWidth: "320px", "cursor": "pointer", border: "2px solid transparent", "&:hover": { border: "2px solid orange" } }} candidate={u} />
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
@ -7,14 +7,25 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Typography,
|
Typography,
|
||||||
Paper,
|
Paper,
|
||||||
|
TextField,
|
||||||
|
Grid,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardActionArea,
|
||||||
Avatar,
|
Avatar,
|
||||||
|
Divider,
|
||||||
|
CircularProgress,
|
||||||
|
Container,
|
||||||
useTheme,
|
useTheme,
|
||||||
Snackbar,
|
Snackbar,
|
||||||
Alert,
|
Alert,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
import PersonIcon from '@mui/icons-material/Person';
|
import PersonIcon from '@mui/icons-material/Person';
|
||||||
import WorkIcon from '@mui/icons-material/Work';
|
import WorkIcon from '@mui/icons-material/Work';
|
||||||
import AssessmentIcon from '@mui/icons-material/Assessment';
|
import AssessmentIcon from '@mui/icons-material/Assessment';
|
||||||
|
import DescriptionIcon from '@mui/icons-material/Description';
|
||||||
|
import FileUploadIcon from '@mui/icons-material/FileUpload';
|
||||||
import { JobMatchAnalysis } from 'components/JobMatchAnalysis';
|
import { JobMatchAnalysis } from 'components/JobMatchAnalysis';
|
||||||
import { Candidate } from "types/types";
|
import { Candidate } from "types/types";
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
@ -26,7 +37,6 @@ import { ComingSoon } from 'components/ui/ComingSoon';
|
|||||||
import { JobManagement } from 'components/JobManagement';
|
import { JobManagement } from 'components/JobManagement';
|
||||||
import { LoginRequired } from 'components/ui/LoginRequired';
|
import { LoginRequired } from 'components/ui/LoginRequired';
|
||||||
import { Scrollable } from 'components/Scrollable';
|
import { Scrollable } from 'components/Scrollable';
|
||||||
import { CandidatePicker } from 'components/ui/CandidatePicker';
|
|
||||||
|
|
||||||
// Main component
|
// Main component
|
||||||
const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
|
const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
|
||||||
@ -41,6 +51,44 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
|||||||
const [activeStep, setActiveStep] = useState(0);
|
const [activeStep, setActiveStep] = useState(0);
|
||||||
const [analysisStarted, setAnalysisStarted] = useState(false);
|
const [analysisStarted, setAnalysisStarted] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const { apiClient } = useAuth();
|
||||||
|
const [candidates, setCandidates] = useState<Candidate[] | null>(null);
|
||||||
|
|
||||||
|
const user_type = user?.userType || 'guest';
|
||||||
|
const user_id = user?.id || '';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (candidates !== null || selectedCandidate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const getCandidates = async () => {
|
||||||
|
try {
|
||||||
|
const results = await apiClient.getCandidates();
|
||||||
|
const candidates: Candidate[] = results.data;
|
||||||
|
candidates.sort((a, b) => {
|
||||||
|
let result = a.lastName.localeCompare(b.lastName);
|
||||||
|
if (result === 0) {
|
||||||
|
result = a.firstName.localeCompare(b.firstName);
|
||||||
|
}
|
||||||
|
if (result === 0) {
|
||||||
|
result = a.username.localeCompare(b.username);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
setCandidates(candidates);
|
||||||
|
} catch (err) {
|
||||||
|
setSnack("" + err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getCandidates();
|
||||||
|
}, [candidates, setSnack]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedCandidate && activeStep === 0) {
|
||||||
|
setActiveStep(1);
|
||||||
|
}
|
||||||
|
}, [selectedCandidate, activeStep]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedJob && activeStep === 1) {
|
if (selectedJob && activeStep === 1) {
|
||||||
@ -50,11 +98,13 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
|||||||
|
|
||||||
// Steps in our process
|
// Steps in our process
|
||||||
const steps = [
|
const steps = [
|
||||||
{ index: 0, label: 'Select Candidate', icon: <PersonIcon /> },
|
|
||||||
{ index: 1, label: 'Job Selection', icon: <WorkIcon /> },
|
{ index: 1, label: 'Job Selection', icon: <WorkIcon /> },
|
||||||
{ index: 2, label: 'Job Analysis', icon: <WorkIcon /> },
|
{ index: 2, label: 'AI Analysis', icon: <WorkIcon /> },
|
||||||
{ index: 3, label: 'Generated Resume', icon: <AssessmentIcon /> }
|
{ index: 3, label: 'Generated Resume', icon: <AssessmentIcon /> }
|
||||||
];
|
];
|
||||||
|
if (!selectedCandidate) {
|
||||||
|
steps.unshift({ index: 0, label: 'Select Candidate', icon: <PersonIcon /> })
|
||||||
|
}
|
||||||
|
|
||||||
// Navigation handlers
|
// Navigation handlers
|
||||||
const handleNext = () => {
|
const handleNext = () => {
|
||||||
@ -76,38 +126,18 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
console.log(activeStep);
|
|
||||||
if (activeStep === 1) {
|
|
||||||
setSelectedCandidate(null);
|
|
||||||
}
|
|
||||||
if (activeStep === 2) {
|
|
||||||
setSelectedJob(null);
|
|
||||||
}
|
|
||||||
setActiveStep((prevActiveStep) => prevActiveStep - 1);
|
setActiveStep((prevActiveStep) => prevActiveStep - 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const moveToStep = (step: number) => {
|
const handleReset = () => {
|
||||||
switch (step) {
|
// setActiveStep(0);
|
||||||
case 0: /* Select candidate */
|
|
||||||
setSelectedCandidate(null);
|
|
||||||
setSelectedJob(null);
|
|
||||||
break;
|
|
||||||
case 1: /* Select Job */
|
|
||||||
setSelectedCandidate(null);
|
|
||||||
setSelectedJob(null);
|
|
||||||
break;
|
|
||||||
case 2: /* Job Analysis */
|
|
||||||
break;
|
|
||||||
case 3: /* Generate Resume */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
setActiveStep(step);
|
|
||||||
}
|
|
||||||
|
|
||||||
const onCandidateSelect = (candidate: Candidate) => {
|
|
||||||
setSelectedCandidate(candidate);
|
|
||||||
setActiveStep(1);
|
setActiveStep(1);
|
||||||
}
|
// setSelectedCandidate(null);
|
||||||
|
setSelectedJob(null);
|
||||||
|
// setJobTitle('');
|
||||||
|
// setJobLocation('');
|
||||||
|
setAnalysisStarted(false);
|
||||||
|
};
|
||||||
|
|
||||||
// Render function for the candidate selection step
|
// Render function for the candidate selection step
|
||||||
const renderCandidateSelection = () => (
|
const renderCandidateSelection = () => (
|
||||||
@ -116,7 +146,77 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
|||||||
Select a Candidate
|
Select a Candidate
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<CandidatePicker onSelect={onCandidateSelect} {...backstoryProps} />
|
{/* <Box sx={{ mb: 3, display: 'flex' }}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
variant="outlined"
|
||||||
|
placeholder="Search candidates by name, title, or location"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={handleSearch} edge="end">
|
||||||
|
<SearchIcon />
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
sx={{ mr: 2 }}
|
||||||
|
/>
|
||||||
|
</Box> */}
|
||||||
|
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
{candidates?.map((candidate) => (
|
||||||
|
<Grid size={{ xs: 12, sm: 6, md: 4 }} key={candidate.id}>
|
||||||
|
<Card
|
||||||
|
elevation={selectedCandidate?.id === candidate.id ? 8 : 1}
|
||||||
|
sx={{
|
||||||
|
height: '100%',
|
||||||
|
borderColor: selectedCandidate?.id === candidate.id ? theme.palette.primary.main : 'transparent',
|
||||||
|
borderWidth: 2,
|
||||||
|
borderStyle: 'solid',
|
||||||
|
transition: 'all 0.3s ease'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardActionArea
|
||||||
|
onClick={() => setSelectedCandidate(candidate)}
|
||||||
|
sx={{ height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'stretch' }}
|
||||||
|
>
|
||||||
|
<CardContent sx={{ flexGrow: 1, p: 3 }}>
|
||||||
|
<Box sx={{ display: 'flex', mb: 2, alignItems: 'center' }}>
|
||||||
|
<Avatar
|
||||||
|
src={candidate.profileImage}
|
||||||
|
alt={candidate.firstName}
|
||||||
|
sx={{ width: 64, height: 64, mr: 2 }}
|
||||||
|
/>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h6" component="div">
|
||||||
|
{candidate.fullName}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary">
|
||||||
|
{candidate.description}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider sx={{ my: 2 }} />
|
||||||
|
|
||||||
|
{candidate.location && <Typography variant="body2" sx={{ mb: 1 }}>
|
||||||
|
<strong>Location:</strong> {candidate.location.country}
|
||||||
|
</Typography>}
|
||||||
|
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||||
|
<strong>Email:</strong> {candidate.email}
|
||||||
|
</Typography>
|
||||||
|
{candidate.phone && <Typography variant="body2">
|
||||||
|
<strong>Phone:</strong> {candidate.phone}
|
||||||
|
</Typography>}
|
||||||
|
</CardContent>
|
||||||
|
</CardActionArea>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -158,49 +258,30 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box sx={{ maxHeight: "100%", position: "relative", display: "flex", flexDirection: "column", overflow: "hidden" }}>
|
||||||
display: "flex", flexDirection: "column",
|
|
||||||
height: "100%", /* Restrict to main-container's height */
|
|
||||||
width: "100%",
|
|
||||||
minHeight: 0,/* Prevent flex overflow */
|
|
||||||
maxHeight: "min-content",
|
|
||||||
"& > *:not(.Scrollable)": {
|
|
||||||
flexShrink: 0, /* Prevent shrinking */
|
|
||||||
},
|
|
||||||
position: "relative",
|
|
||||||
}}>
|
|
||||||
<Paper elevation={4} sx={{ m: 0, borderRadius: 0, mb: 1, p: 0 }}>{selectedCandidate && <CandidateInfo variant="small" candidate={selectedCandidate} sx={{ width: "100%" }} />}</Paper>
|
<Paper elevation={4} sx={{ m: 0, borderRadius: 0, mb: 1, p: 0 }}>{selectedCandidate && <CandidateInfo variant="small" candidate={selectedCandidate} sx={{ width: "100%" }} />}</Paper>
|
||||||
<Scrollable
|
<Scrollable sx={{ display: "flex", flexGrow: 1, maxHeight: "calc(100dvh - 234px)", position: "relative" }}>
|
||||||
sx={{
|
|
||||||
position: "relative",
|
|
||||||
maxHeight: "100%",
|
|
||||||
width: "100%",
|
|
||||||
display: "flex", flexGrow: 1,
|
|
||||||
flex: 1, /* Take remaining space in some-container */
|
|
||||||
overflowY: "auto", /* Scroll if content overflows */
|
|
||||||
}}>
|
|
||||||
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
<Box sx={{ display: "flex", justifyContent: "center" }}>
|
||||||
<Typography variant="subtitle1" color="text.secondary" gutterBottom>
|
<Typography variant="subtitle1" color="text.secondary" gutterBottom>
|
||||||
Match candidates to job requirements with AI-powered analysis
|
Match candidates to job requirements with AI-powered analysis
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ mt: 4, mb: 4 }}>
|
<Box sx={{ mt: 4, mb: 4 }}>
|
||||||
<Stepper activeStep={activeStep} alternativeLabel>
|
<Stepper activeStep={activeStep} alternativeLabel>
|
||||||
{steps.map((step, index) => (
|
{steps.map(step => (
|
||||||
<Step>
|
<Step key={step.index}>
|
||||||
<StepLabel sx={{ cursor: "pointer" }} onClick={() => { moveToStep(index); }}
|
<StepLabel slots={{
|
||||||
slots={{
|
stepIcon: () => (
|
||||||
stepIcon: () => (
|
<Avatar
|
||||||
<Avatar key={step.index}
|
sx={{
|
||||||
sx={{
|
bgcolor: activeStep >= step.index ? theme.palette.primary.main : theme.palette.grey[300],
|
||||||
bgcolor: activeStep >= step.index ? theme.palette.primary.main : theme.palette.grey[300],
|
color: 'white'
|
||||||
color: 'white'
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{step.icon}
|
||||||
{step.icon}
|
</Avatar>
|
||||||
</Avatar>
|
)
|
||||||
)
|
}}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{step.label}
|
{step.label}
|
||||||
</StepLabel>
|
</StepLabel>
|
||||||
@ -213,7 +294,6 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
|||||||
{activeStep === 1 && renderJobDescription()}
|
{activeStep === 1 && renderJobDescription()}
|
||||||
{activeStep === 2 && renderAnalysis()}
|
{activeStep === 2 && renderAnalysis()}
|
||||||
{activeStep === 3 && renderResume()}
|
{activeStep === 3 && renderResume()}
|
||||||
</Scrollable>
|
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
|
||||||
<Button
|
<Button
|
||||||
@ -227,7 +307,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
|||||||
<Box sx={{ flex: '1 1 auto' }} />
|
<Box sx={{ flex: '1 1 auto' }} />
|
||||||
|
|
||||||
{activeStep === steps[steps.length - 1].index ? (
|
{activeStep === steps[steps.length - 1].index ? (
|
||||||
<Button onClick={() => { moveToStep(0) }} variant="outlined">
|
<Button onClick={handleReset} variant="outlined">
|
||||||
Start New Analysis
|
Start New Analysis
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
@ -247,7 +327,8 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
|||||||
<Alert onClose={() => setError(null)} severity="error" sx={{ width: '100%' }}>
|
<Alert onClose={() => setError(null)} severity="error" sx={{ width: '100%' }}>
|
||||||
{error}
|
{error}
|
||||||
</Alert>
|
</Alert>
|
||||||
</Snackbar>
|
</Snackbar>
|
||||||
|
</Scrollable>
|
||||||
</Box>);
|
</Box>);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user