import React, { JSX, useEffect, useRef, useState } from 'react'; import { Box, Link, Typography, SxProps, Chip, LinearProgress, IconButton, Tooltip, } from '@mui/material'; import { useTheme } from '@mui/material'; import DeleteIcon from '@mui/icons-material/Delete'; import CloseIcon from '@mui/icons-material/Close'; import { useMediaQuery } from '@mui/material'; import { Job } from 'types/types'; import { rest } from 'lodash'; import { useAuth } from 'hooks/AuthContext'; import { Build, CheckCircle, Description, Psychology, Star, Work } from '@mui/icons-material'; import ModelTrainingIcon from '@mui/icons-material/ModelTraining'; import { StatusIcon, StatusBox } from 'components/ui/StatusIcon'; import RestoreIcon from '@mui/icons-material/Restore'; import SaveIcon from '@mui/icons-material/Save'; import * as Types from 'types/types'; import { useAppState } from 'hooks/GlobalContext'; import { StyledMarkdown } from 'components/StyledMarkdown'; interface JobInfoProps { job: Job; sx?: SxProps; action?: string; elevation?: number; variant?: 'minimal' | 'small' | 'normal' | 'all' | null; onClose?: () => void; inDialog?: boolean; // Whether this is rendered in a dialog } const JobInfo: React.FC = (props: JobInfoProps) => { const { setSnack } = useAppState(); const { user, apiClient } = useAuth(); const { sx, variant = 'normal', job, onClose, inDialog = false } = props; const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')) || variant === 'minimal'; const isAdmin = user?.isAdmin; const [adminStatus, setAdminStatus] = useState(null); const [adminStatusType, setAdminStatusType] = useState(null); const [activeJob, setActiveJob] = useState({ ...job, }); /* Copy of job */ // State for description expansion const [isDescriptionExpanded, setIsDescriptionExpanded] = useState(false); const [shouldShowMoreButton, setShouldShowMoreButton] = useState(false); const [deleted, setDeleted] = useState(false); const descriptionRef = useRef(null); useEffect(() => { if (job && job.id !== activeJob?.id) { setActiveJob(job); } }, [job, activeJob, setActiveJob]); // Check if description needs truncation useEffect(() => { if (descriptionRef.current && job.summary) { const element = descriptionRef.current; // Check if the scrollHeight is greater than clientHeight (meaning content is truncated) setShouldShowMoreButton(element.scrollHeight > element.clientHeight); } }, [job.summary]); const deleteJob = async (jobId: string | undefined): Promise => { if (jobId) { await apiClient.deleteJob(jobId); } }; const handleReset = async (): Promise => { setActiveJob({ ...job }); }; if (!job) { return No job provided.; } const handleSave = async (): Promise => { const newJob = await apiClient.updateJob(job.id || '', { description: activeJob.description, requirements: activeJob.requirements, }); job.updatedAt = newJob.updatedAt; setActiveJob(newJob); setSnack('Job updated.'); }; const handleRefresh = (): void => { setAdminStatus('Re-extracting Job information...'); const jobStatusHandlers = { onStatus: (status: Types.ChatMessageStatus): void => { console.log('status:', status.content); setAdminStatusType(status.activity); setAdminStatus(status.content); }, onMessage: async (jobRequirementsMessage: Types.JobRequirementsMessage): Promise => { console.log('onMessage - job', jobRequirementsMessage); setActiveJob(jobRequirementsMessage.job); }, onError: (error: Types.ChatMessageError): void => { console.log('onError', error); setAdminStatusType(null); setAdminStatus(null); }, onComplete: (): void => { setAdminStatusType(null); setAdminStatus(null); }, }; apiClient.regenerateJob(activeJob, jobStatusHandlers); }; const renderRequirementSection = ( title: string, items: string[] | undefined, icon: JSX.Element, required = false ): JSX.Element => { if (!items || items.length === 0) return <>; return ( {icon} {title} {required && ( )} {items.map((item, index) => ( {item} ))} ); }; const renderJobRequirements = (): JSX.Element => { if (!activeJob.requirements) return <>; return ( Job Requirements Analysis {renderRequirementSection( 'Technical Skills (Required)', activeJob.requirements.technicalSkills.required, , true )} {renderRequirementSection( 'Technical Skills (Preferred)', activeJob.requirements.technicalSkills.preferred, )} {renderRequirementSection( 'Experience Requirements (Required)', activeJob.requirements.experienceRequirements.required, , true )} {renderRequirementSection( 'Experience Requirements (Preferred)', activeJob.requirements.experienceRequirements.preferred, )} {renderRequirementSection( 'Soft Skills', activeJob.requirements.softSkills, )} {renderRequirementSection( 'Experience', activeJob.requirements.experience, )} {renderRequirementSection( 'Education', activeJob.requirements.education, )} {renderRequirementSection( 'Certifications', activeJob.requirements.certifications, )} {renderRequirementSection( 'Preferred Attributes', activeJob.requirements.preferredAttributes, )} ); }; return ( {onClose && !inDialog && ( )} div > div > :first-of-type': { fontWeight: 'bold', whiteSpace: 'nowrap', }, '& > div > div > :last-of-type': { mb: 0.75, mr: 1 }, position: 'relative', }} > {activeJob.company && ( Company {activeJob.company} )} {activeJob.title && ( Title {activeJob.title} )} {!isMobile && activeJob.summary && ( Summary {activeJob.summary} {shouldShowMoreButton && ( { e.preventDefault(); e.stopPropagation(); setIsDescriptionExpanded(!isDescriptionExpanded); }} sx={{ color: theme.palette.primary.main, textDecoration: 'none', cursor: 'pointer', fontSize: '0.725rem', fontWeight: 500, mt: 0.5, display: 'block', '&:hover': { textDecoration: 'underline', }, }} > [{isDescriptionExpanded ? 'less' : 'more'}] )} )} {variant !== 'small' && variant !== 'minimal' && ( <> {job.details?.employmentType && ( )} {activeJob.details?.location && ( 📍 {activeJob.details.location.city},{' '} {activeJob.details.location.state || activeJob.details.location.country} )} {activeJob.owner && ( Submitted by: {activeJob.owner.fullName} )} {activeJob.createdAt && ( Created: {activeJob.createdAt.toISOString()} )} {activeJob.updatedAt && ( Updated: {activeJob.updatedAt.toISOString()} )} Job ID: {job.id} )} {variant === 'all' && ( )} {variant !== 'small' && variant !== 'minimal' && renderJobRequirements()} {isAdmin && ( {(job.updatedAt && job.updatedAt.toISOString()) !== (activeJob.updatedAt && activeJob.updatedAt.toISOString()) && ( { e.stopPropagation(); handleSave(); }} > )} { e.stopPropagation(); deleteJob(job.id); setDeleted(true); }} > { e.stopPropagation(); handleReset(); }} > { e.stopPropagation(); handleRefresh(); }} > {adminStatus && ( {adminStatusType && } {adminStatus || 'Processing...'} {adminStatus && } )} )} ); }; export { JobInfo };