import React, { JSX, useActionState, useEffect, useRef, useState } from 'react'; import { Box, Link, Typography, Avatar, Grid, SxProps, CardActions, Chip, Stack, CardHeader, Button, styled, LinearProgress, IconButton, Tooltip } from '@mui/material'; import { Card, CardContent, Divider, useTheme, } from '@mui/material'; import DeleteIcon from '@mui/icons-material/Delete'; import { useMediaQuery } from '@mui/material'; import { Job } from 'types/types'; import { CopyBubble } from "components/CopyBubble"; import { rest } from 'lodash'; import { AIBanner } from 'components/ui/AIBanner'; import { useAuth } from 'hooks/AuthContext'; import { DeleteConfirmation } from '../DeleteConfirmation'; 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 }; const JobInfo: React.FC = (props: JobInfoProps) => { const { setSnack } = useAppState(); const { job } = props; const { user, apiClient } = useAuth(); const { sx, action = '', elevation = 1, variant = "normal" } = 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) => { if (jobId) { await apiClient.deleteJob(jobId); } } const handleReset = async () => { setActiveJob({ ...job }); } if (!job) { return No job provided.; } const handleSave = async () => { const newJob = await apiClient.updateJob(job.id || '', { description: activeJob.description, requirements: activeJob.requirements, }); job.updatedAt = newJob.updatedAt; setActiveJob(newJob) setSnack('Job updated.'); } const handleRefresh = () => { setAdminStatus("Re-extracting Job information..."); const jobStatusHandlers = { onStatus: (status: Types.ChatMessageStatus) => { console.log('status:', status.content); setAdminStatusType(status.activity); setAdminStatus(status.content); }, onMessage: async (jobMessage: Types.JobRequirementsMessage) => { const newJob: Types.Job = jobMessage.job console.log('onMessage - job', newJob); newJob.id = job.id; newJob.createdAt = job.createdAt; const updatedJob: Types.Job = await apiClient.updateJob(job.id || '', newJob); setActiveJob(updatedJob); }, onError: (error: Types.ChatMessageError) => { console.log('onError', error); setAdminStatusType(null); setAdminStatus(null); }, onComplete: () => { setAdminStatusType(null); setAdminStatus(null); } }; apiClient.createJobFromDescription(activeJob.description, jobStatusHandlers); }; 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 (!activeJob.requirements) return null; return ( } sx={{ p: 0, pb: 1 }} /> {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 ( div > div > :first-of-type": { fontWeight: "bold", whiteSpace: "nowrap" }, "& > div > div > :last-of-type": { mb: 0.75, mr: 1 } }}> {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") && <> {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 };