import React, { useEffect, useRef, useState } from 'react'; import { Box, Typography, Grid, SxProps, Stack, CardHeader, Button, LinearProgress, IconButton, Tooltip, Card, CardContent, Divider, useTheme, useMediaQuery, Dialog, DialogTitle, DialogContent, DialogActions, Tabs, Tab, Paper, } from '@mui/material'; import PrintIcon from '@mui/icons-material/Print'; import { Delete as DeleteIcon, Restore as RestoreIcon, Save as SaveIcon, Edit as EditIcon, Description as DescriptionIcon, Work as WorkIcon, Person as PersonIcon, Schedule as ScheduleIcon, ModelTraining, } from '@mui/icons-material'; import InputIcon from '@mui/icons-material/Input'; import TuneIcon from '@mui/icons-material/Tune'; import PreviewIcon from '@mui/icons-material/Preview'; import EditDocumentIcon from '@mui/icons-material/EditDocument'; import { useReactToPrint } from 'react-to-print'; import { useAuth } from 'hooks/AuthContext'; import { useAppState } from 'hooks/GlobalContext'; import { StyledMarkdown } from 'components/StyledMarkdown'; import { Resume } from 'types/types'; import { BackstoryTextField } from 'components/BackstoryTextField'; import { JobInfo } from './JobInfo'; import './ResumeInfo.css'; import { Scrollable } from 'components/Scrollable'; import * as Types from 'types/types'; import { StreamingOptions } from 'services/api-client'; import { StatusBox, StatusIcon } from './StatusIcon'; interface ResumeInfoProps { resume: Resume; sx?: SxProps; action?: string; elevation?: number; variant?: 'minimal' | 'small' | 'normal' | 'all' | null; } const ResumeInfo: React.FC = (props: ResumeInfoProps) => { const { setSnack } = useAppState(); const { resume } = props; const { user, apiClient } = useAuth(); const { sx, variant = 'normal' } = props; const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')) || variant === 'minimal'; const isAdmin = user?.isAdmin; const [activeResume, setActiveResume] = useState({ ...resume }); const [deleted, setDeleted] = useState(false); const [editDialogOpen, setEditDialogOpen] = useState(false); const [editContent, setEditContent] = useState(''); const [editSystemPrompt, setEditSystemPrompt] = useState(''); const [editPrompt, setEditPrompt] = useState(''); const [saving, setSaving] = useState(false); const [tabValue, setTabValue] = useState('markdown'); const [status, setStatus] = useState(''); const [statusType, setStatusType] = useState(null); const [error, setError] = useState(null); const printContentRef = useRef(null); const reactToPrintFn = useReactToPrint({ contentRef: printContentRef, pageStyle: '@page { margin: 8mm !important; }', }); useEffect(() => { if (resume && resume.id !== activeResume?.id) { setActiveResume(resume); } }, [resume, activeResume]); // Check if content needs truncation const deleteResume = async (id: string | undefined) => { if (id) { try { await apiClient.deleteResume(id); setDeleted(true); setSnack('Resume deleted successfully.'); } catch (error) { setSnack('Failed to delete resume.'); } } }; const handleReset = async () => { setActiveResume({ ...resume }); }; const handleSave = async () => { setSaving(true); try { const resumeUpdate = { ...activeResume, resume: editContent, systemPrompt: editSystemPrompt, prompt: editPrompt, }; const result = await apiClient.updateResume(resumeUpdate); console.log('Resume updated:', result); const updatedResume = { ...activeResume, ...result, }; setActiveResume(updatedResume); setSnack('Resume updated successfully.'); } catch (error) { setSnack('Failed to update resume.'); } finally { setSaving(false); } }; const handleEditOpen = () => { setEditContent(activeResume.resume); setEditSystemPrompt(activeResume.systemPrompt || ''); setEditPrompt(activeResume.prompt || ''); setEditDialogOpen(true); }; if (!resume) { return No resume provided.; } const formatDate = (date: Date | undefined) => { if (!date) return 'N/A'; try { return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit', }).format(date); } catch (error) { console.error('Error formatting date:', error); return 'Invalid date'; } }; const generateResumeHandlers: StreamingOptions = { onMessage: (message: Types.ChatMessageResume) => { const resume: Resume = message.resume; setEditSystemPrompt(resume.systemPrompt || ''); setEditPrompt(resume.prompt || ''); setEditContent(resume.resume); setActiveResume({ ...resume }); setStatus(''); setSnack('Resume generation completed successfully.'); }, onStreaming: (chunk: Types.ChatMessageStreaming) => { if (status === '') { setStatus('Generating resume...'); setStatusType('generating'); } setEditContent(chunk.content); }, onStatus: (status: Types.ChatMessageStatus) => { console.log('status:', status.content); setStatusType(status.activity); setStatus(status.content); }, onError: (error: Types.ChatMessageError) => { console.log('error:', error); setStatusType(null); setStatus(error.content); setError(error); }, }; const generateResume = async (): Promise => { setStatusType('thinking'); setStatus('Starting resume generation...'); setActiveResume({ ...activeResume, resume: '' }); // Reset resume content const request = await apiClient.generateResume( activeResume.candidateId || '', activeResume.jobId || '', generateResumeHandlers ); await request.promise; }; const handleTabChange = (event: React.SyntheticEvent, newValue: string) => { if (newValue === 'print') { reactToPrintFn(); return; } if (newValue === 'regenerate') { // Handle resume regeneration logic here setSnack('Regenerating resume...'); generateResume(); return; } setTabValue(newValue); }; return ( {/* Header Information */} {activeResume.candidate && ( Candidate )} {activeResume.candidate?.fullName || activeResume.candidateId} {activeResume.job && ( <> Job {activeResume.job.title} at {activeResume.job.company} )} Timeline Created: {formatDate(activeResume.createdAt)} Updated: {formatDate(activeResume.updatedAt)} Resume ID: {activeResume.id} {/* Resume Content */} {activeResume.resume && ( } sx={{ p: 0, pb: 1 }} action={ isAdmin && ( ) } /> {activeResume.resume} )} {variant === 'all' && activeResume.resume && ( )} {/* Admin Controls */} {isAdmin && ( { e.stopPropagation(); handleEditOpen(); }} > { e.stopPropagation(); deleteResume(activeResume.id); }} > { e.stopPropagation(); handleReset(); }} > {saving && ( Saving resume... )} )} {/* Edit Dialog */} { setEditDialogOpen(false); }} maxWidth="lg" fullWidth disableEscapeKeyDown={true} fullScreen={true} > Edit Resume Content Resume for {activeResume.candidate?.fullName || activeResume.candidateId},{' '} {activeResume.job?.title || 'No Job Title Assigned'},{' '} {activeResume.job?.company || 'No Company Assigned'} Resume ID: # {activeResume.id} Last saved:{' '} {activeResume.updatedAt ? new Date(activeResume.updatedAt).toLocaleString() : 'N/A'} } label="Markdown" /> {activeResume.systemPrompt && ( } label="System Prompt" /> )} {activeResume.systemPrompt && ( } label="Prompt" /> )} } label="Preview" /> } label="Print" /> } label="Regenerate" /> {status && ( {statusType && } {status || 'Processing...'} {status && !error && } )} *:not(.Scrollable)': { flexShrink: 0 /* Prevent shrinking */, }, position: 'relative', }} > {tabValue === 'markdown' && ( setEditContent(value)} style={{ position: 'relative', maxHeight: '100%', height: '100%', width: '100%', display: 'flex', minHeight: '100%', flexGrow: 1, flex: 1 /* Take remaining space in some-container */, overflowY: 'auto' /* Scroll if content overflows */, }} placeholder="Enter resume content..." /> )} {tabValue === 'systemPrompt' && ( setEditSystemPrompt(value)} style={{ position: 'relative', maxHeight: '100%', // height: '100%', width: '100%', display: 'flex', minHeight: '100%', flexGrow: 1, flex: 1 /* Take remaining space in some-container */, overflowY: 'auto' /* Scroll if content overflows */, }} placeholder="Edit system prompt..." /> )} {tabValue === 'prompt' && ( setEditPrompt(value)} style={{ position: 'relative', maxHeight: '100%', height: '100%', width: '100%', display: 'flex', minHeight: '100%', flexGrow: 1, flex: 1 /* Take remaining space in some-container */, overflowY: 'auto' /* Scroll if content overflows */, }} placeholder="Edit prompt..." /> )} {tabValue === 'preview' && (   )} {activeResume.job !== undefined && ( )} ); }; export { ResumeInfo };