import React, { useState, useEffect, useRef, JSX } from 'react'; import { Box, Button, Typography, Paper, TextField, Grid, Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, IconButton, useTheme, useMediaQuery, Chip, Divider, Card, CardContent, CardHeader, LinearProgress, Stack, Alert } from '@mui/material'; import { SyncAlt, Favorite, Settings, Info, Search, AutoFixHigh, Image, Psychology, Build, CloudUpload, Description, Business, LocationOn, Work, CheckCircle, Star } from '@mui/icons-material'; import { styled } from '@mui/material/styles'; import DescriptionIcon from '@mui/icons-material/Description'; import FileUploadIcon from '@mui/icons-material/FileUpload'; import { useAuth } from 'hooks/AuthContext'; import { useAppState, useSelectedCandidate, useSelectedJob } from 'hooks/GlobalContext'; import { BackstoryElementProps } from './BackstoryTab'; import { LoginRequired } from 'components/ui/LoginRequired'; import * as Types from 'types/types'; const VisuallyHiddenInput = styled('input')({ clip: 'rect(0 0 0 0)', clipPath: 'inset(50%)', height: 1, overflow: 'hidden', position: 'absolute', bottom: 0, left: 0, whiteSpace: 'nowrap', width: 1, }); const UploadBox = styled(Box)(({ theme }) => ({ border: `2px dashed ${theme.palette.primary.main}`, borderRadius: theme.shape.borderRadius * 2, padding: theme.spacing(4), textAlign: 'center', backgroundColor: theme.palette.action.hover, transition: 'all 0.3s ease', cursor: 'pointer', '&:hover': { backgroundColor: theme.palette.action.selected, borderColor: theme.palette.primary.dark, }, })); const StatusBox = styled(Box)(({ theme }) => ({ display: 'flex', alignItems: 'center', gap: theme.spacing(1), padding: theme.spacing(1, 2), backgroundColor: theme.palette.background.paper, borderRadius: theme.shape.borderRadius, border: `1px solid ${theme.palette.divider}`, minHeight: 48, })); const getIcon = (type: Types.ApiActivityType) => { switch (type) { case 'converting': return ; case 'heartbeat': return ; case 'system': return ; case 'info': return ; case 'searching': return ; case 'generating': return ; case 'generating_image': return ; case 'thinking': return ; case 'tooling': return ; default: return ; } }; interface JobCreator extends BackstoryElementProps { onSave?: (job: Types.Job) => void; } const JobCreator = (props: JobCreator) => { const { user, apiClient } = useAuth(); const { onSave } = props; const { selectedCandidate } = useSelectedCandidate(); const { selectedJob, setSelectedJob } = useSelectedJob(); const { setSnack } = useAppState(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const isTablet = useMediaQuery(theme.breakpoints.down('md')); const [openUploadDialog, setOpenUploadDialog] = useState(false); const [jobDescription, setJobDescription] = useState(''); const [jobRequirements, setJobRequirements] = useState(null); const [jobTitle, setJobTitle] = useState(''); const [company, setCompany] = useState(''); const [summary, setSummary] = useState(''); const [jobLocation, setJobLocation] = useState(''); const [jobId, setJobId] = useState(''); const [jobStatus, setJobStatus] = useState(''); const [jobStatusIcon, setJobStatusIcon] = useState(<>); const [isProcessing, setIsProcessing] = useState(false); useEffect(() => { }, [jobTitle, jobDescription, company]); const fileInputRef = useRef(null); if (!user?.id) { return ( ); } const jobStatusHandlers = { onStatus: (status: Types.ChatMessageStatus) => { console.log('status:', status.content); setJobStatusIcon(getIcon(status.activity)); setJobStatus(status.content); }, onMessage: (job: Types.Job) => { console.log('onMessage - job', job); setCompany(job.company || ''); setJobDescription(job.description); setSummary(job.summary || ''); setJobTitle(job.title || ''); setJobRequirements(job.requirements || null); setJobStatusIcon(<>); setJobStatus(''); }, onError: (error: Types.ChatMessageError) => { console.log('onError', error); setSnack(error.content, "error"); setIsProcessing(false); }, onComplete: () => { setJobStatusIcon(<>); setJobStatus(''); setIsProcessing(false); } }; const handleJobUpload = async (e: React.ChangeEvent) => { if (e.target.files && e.target.files[0]) { const file = e.target.files[0]; const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase(); let docType: Types.DocumentType | null = null; switch (fileExtension.substring(1)) { case "pdf": docType = "pdf"; break; case "docx": docType = "docx"; break; case "md": docType = "markdown"; break; case "txt": docType = "txt"; break; } if (!docType) { setSnack('Invalid file type. Please upload .txt, .md, .docx, or .pdf files only.', 'error'); return; } try { setIsProcessing(true); setJobDescription(''); setJobTitle(''); setJobRequirements(null); setSummary(''); const controller = apiClient.createJobFromFile(file, jobStatusHandlers); const job = await controller.promise; if (!job) { return; } console.log(`Job id: ${job.id}`); e.target.value = ''; } catch (error) { console.error(error); setSnack('Failed to upload document', 'error'); setIsProcessing(false); } } }; const handleUploadClick = () => { fileInputRef.current?.click(); }; const renderRequirementSection = (title: string, items: string[] | undefined, icon: JSX.Element, required = false) => { if (!items || items.length === 0) return null; return ( {icon} {title} {required && } {items.map((item, index) => ( ))} ); }; const renderJobRequirements = () => { if (!jobRequirements) return null; return ( } sx={{ pb: 1 }} /> {renderRequirementSection( "Technical Skills (Required)", jobRequirements.technicalSkills.required, , true )} {renderRequirementSection( "Technical Skills (Preferred)", jobRequirements.technicalSkills.preferred, )} {renderRequirementSection( "Experience Requirements (Required)", jobRequirements.experienceRequirements.required, , true )} {renderRequirementSection( "Experience Requirements (Preferred)", jobRequirements.experienceRequirements.preferred, )} {renderRequirementSection( "Soft Skills", jobRequirements.softSkills, )} {renderRequirementSection( "Experience", jobRequirements.experience, )} {renderRequirementSection( "Education", jobRequirements.education, )} {renderRequirementSection( "Certifications", jobRequirements.certifications, )} {renderRequirementSection( "Preferred Attributes", jobRequirements.preferredAttributes, )} ); }; const handleSave = async () => { const newJob: Types.Job = { ownerId: user?.id || '', ownerType: 'candidate', description: jobDescription, company: company, summary: summary, title: jobTitle, requirements: jobRequirements || undefined, createdAt: new Date(), updatedAt: new Date(), }; setIsProcessing(true); const job = await apiClient.createJob(newJob); setIsProcessing(false); onSave ? onSave(job) : setSelectedJob(job); }; const handleExtractRequirements = async () => { try { setIsProcessing(true); const controller = apiClient.createJobFromDescription(jobDescription, jobStatusHandlers); const job = await controller.promise; if (!job) { setIsProcessing(false); return; } console.log(`Job id: ${job.id}`); } catch (error) { console.error(error); setSnack('Failed to upload document', 'error'); setIsProcessing(false); } setIsProcessing(false); }; const renderJobCreation = () => { if (!user) { return You must be logged in; } return ( {/* Upload Section */} } /> Upload Job Description Drop your job description here Supported formats: PDF, DOCX, TXT, MD Or Enter Manually setJobDescription(e.target.value)} disabled={isProcessing} sx={{ mb: 2 }} /> {jobRequirements === null && jobDescription && ( )} {(jobStatus || isProcessing) && ( {jobStatusIcon} {jobStatus || 'Processing...'} {isProcessing && } )} {/* Job Details Section */} } /> setJobTitle(e.target.value)} required disabled={isProcessing} InputProps={{ startAdornment: }} /> setCompany(e.target.value)} required disabled={isProcessing} InputProps={{ startAdornment: }} /> {/* setJobLocation(e.target.value)} disabled={isProcessing} InputProps={{ startAdornment: }} /> */} {/* Job Summary */} {summary !== '' && } sx={{ pb: 1 }} /> {summary} } {/* Requirements Display */} {renderJobRequirements()} ); }; return ( {selectedJob === null && renderJobCreation()} ); }; export { JobCreator };