Lots of mobile and desktop edit view tweaks
This commit is contained in:
parent
97272d9175
commit
064868e96e
@ -6,8 +6,9 @@ import React, {
|
|||||||
useState,
|
useState,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { SxProps, useTheme } from '@mui/material/styles';
|
||||||
import './BackstoryTextField.css';
|
import './BackstoryTextField.css';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
|
||||||
// Define ref interface for exposed methods
|
// Define ref interface for exposed methods
|
||||||
interface BackstoryTextFieldRef {
|
interface BackstoryTextFieldRef {
|
||||||
@ -23,11 +24,12 @@ interface BackstoryTextFieldProps {
|
|||||||
onEnter?: (value: string) => void;
|
onEnter?: (value: string) => void;
|
||||||
onChange?: (value: string) => void;
|
onChange?: (value: string) => void;
|
||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
|
sx?: SxProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BackstoryTextField = React.forwardRef<BackstoryTextFieldRef, BackstoryTextFieldProps>(
|
const BackstoryTextField = React.forwardRef<BackstoryTextFieldRef, BackstoryTextFieldProps>(
|
||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const { value = '', disabled = false, placeholder, onEnter, onChange, style } = props;
|
const { value = '', disabled = false, placeholder, onEnter, onChange, style, sx } = props;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const shadowRef = useRef<HTMLTextAreaElement>(null);
|
const shadowRef = useRef<HTMLTextAreaElement>(null);
|
||||||
@ -115,7 +117,7 @@ const BackstoryTextField = React.forwardRef<BackstoryTextFieldRef, BackstoryText
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box sx={{ display: 'flex', flexGrow: 1, boxSizing: 'border-box', p: 1, ...sx }}>
|
||||||
<textarea
|
<textarea
|
||||||
className="BackstoryTextField"
|
className="BackstoryTextField"
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
@ -148,7 +150,7 @@ const BackstoryTextField = React.forwardRef<BackstoryTextFieldRef, BackstoryText
|
|||||||
readOnly
|
readOnly
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
/>
|
/>
|
||||||
</>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -5,13 +5,11 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
SxProps,
|
SxProps,
|
||||||
Chip,
|
Chip,
|
||||||
Stack,
|
|
||||||
CardHeader,
|
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
IconButton,
|
IconButton,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Card, CardContent, Divider, useTheme } from '@mui/material';
|
import { useTheme } from '@mui/material';
|
||||||
import DeleteIcon from '@mui/icons-material/Delete';
|
import DeleteIcon from '@mui/icons-material/Delete';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import { useMediaQuery } from '@mui/material';
|
import { useMediaQuery } from '@mui/material';
|
||||||
@ -128,7 +126,7 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
|
|||||||
if (!items || items.length === 0) return <></>;
|
if (!items || items.length === 0) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ mb: 2 }}>
|
<Box sx={{ mb: 2, display: 'flex', position: 'relative', flexDirection: 'column' }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1.5 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1.5 }}>
|
||||||
{icon}
|
{icon}
|
||||||
<Typography
|
<Typography
|
||||||
@ -146,17 +144,22 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5, position: 'relative' }}>
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
<Chip
|
<Box
|
||||||
key={index}
|
key={index}
|
||||||
label={item}
|
sx={{
|
||||||
variant="outlined"
|
border: '1px solid grey',
|
||||||
size="small"
|
p: 0.5,
|
||||||
sx={{ mb: 1, fontSize: '0.75rem !important' }}
|
borderRadius: 1,
|
||||||
/>
|
fontSize: '0.75rem !important',
|
||||||
|
display: 'flex',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -165,13 +168,24 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
|
|||||||
if (!activeJob.requirements) return <></>;
|
if (!activeJob.requirements) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card elevation={0} sx={{ m: 0, p: 0, mt: 2, background: 'transparent !important' }}>
|
<Box
|
||||||
<CardHeader
|
sx={{
|
||||||
title="Job Requirements Analysis"
|
maxWidth: '100%',
|
||||||
avatar={<CheckCircle color="success" />}
|
position: 'relative',
|
||||||
sx={{ p: 0, pb: 1 }}
|
m: 0,
|
||||||
/>
|
p: 0,
|
||||||
<CardContent sx={{ p: 0 }}>
|
background: 'transparent !important',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{ p: 0, pb: 1, alignItems: 'center', display: 'flex', flexDirection: 'row', gap: 1 }}
|
||||||
|
>
|
||||||
|
<CheckCircle color="success" />
|
||||||
|
<Box>Job Requirements Analysis</Box>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ p: 0 }}>
|
||||||
{renderRequirementSection(
|
{renderRequirementSection(
|
||||||
'Technical Skills (Required)',
|
'Technical Skills (Required)',
|
||||||
activeJob.requirements.technicalSkills.required,
|
activeJob.requirements.technicalSkills.required,
|
||||||
@ -219,8 +233,8 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
|
|||||||
activeJob.requirements.preferredAttributes,
|
activeJob.requirements.preferredAttributes,
|
||||||
<Star color="secondary" />
|
<Star color="secondary" />
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</Box>
|
||||||
</Card>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -232,9 +246,9 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
|
|||||||
borderColor: 'transparent',
|
borderColor: 'transparent',
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
transition: 'all 0.3s ease',
|
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
minWidth: 0,
|
minWidth: 0,
|
||||||
|
maxWidth: '100%',
|
||||||
opacity: deleted ? 0.5 : 1.0,
|
opacity: deleted ? 0.5 : 1.0,
|
||||||
backgroundColor: deleted
|
backgroundColor: deleted
|
||||||
? theme.palette.action.disabledBackground
|
? theme.palette.action.disabledBackground
|
||||||
@ -273,6 +287,7 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
|
|||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
},
|
},
|
||||||
'& > div > div > :last-of-type': { mb: 0.75, mr: 1 },
|
'& > div > div > :last-of-type': { mb: 0.75, mr: 1 },
|
||||||
|
position: 'relative',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
@ -401,12 +416,7 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
|
|||||||
<StyledMarkdown sx={{ display: 'flex' }} content={activeJob.description} />
|
<StyledMarkdown sx={{ display: 'flex' }} content={activeJob.description} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{variant !== 'small' && variant !== 'minimal' && (
|
{variant !== 'small' && variant !== 'minimal' && renderJobRequirements()}
|
||||||
<Box>
|
|
||||||
<Divider />
|
|
||||||
{renderJobRequirements()}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isAdmin && (
|
{isAdmin && (
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', p: 1 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', p: 1 }}>
|
||||||
|
@ -20,7 +20,7 @@ import { useAppState, useSelectedCandidate } from 'hooks/GlobalContext';
|
|||||||
import PropagateLoader from 'react-spinners/PropagateLoader';
|
import PropagateLoader from 'react-spinners/PropagateLoader';
|
||||||
import { BackstoryTextField, BackstoryTextFieldRef } from 'components/BackstoryTextField';
|
import { BackstoryTextField, BackstoryTextFieldRef } from 'components/BackstoryTextField';
|
||||||
import { Scrollable } from 'components/Scrollable';
|
import { Scrollable } from 'components/Scrollable';
|
||||||
import { StyledMarkdown } from './StyledMarkdown';
|
import { StyledMarkdown } from '../StyledMarkdown';
|
||||||
|
|
||||||
const emptyMetadata: ChatMessageMetaData = {
|
const emptyMetadata: ChatMessageMetaData = {
|
||||||
model: 'qwen2.5',
|
model: 'qwen2.5',
|
||||||
@ -259,6 +259,7 @@ const ResumeChat = forwardRef<ConversationHandle, ResumeChatProps>(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
className="ResumeChat"
|
||||||
ref={ref}
|
ref={ref}
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -358,7 +359,18 @@ const ResumeChat = forwardRef<ConversationHandle, ResumeChatProps>(
|
|||||||
</Scrollable>
|
</Scrollable>
|
||||||
)}
|
)}
|
||||||
{/* Fixed Message Input */}
|
{/* Fixed Message Input */}
|
||||||
<Box sx={{ display: 'flex', flexShrink: 1, gap: 1 }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexGrow: 1,
|
||||||
|
gap: 1,
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
minWidth: '100%',
|
||||||
|
flexDirection: 'row',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DeleteConfirmation
|
<DeleteConfirmation
|
||||||
onDelete={(): void => {
|
onDelete={(): void => {
|
||||||
chatSession && onDelete(chatSession);
|
chatSession && onDelete(chatSession);
|
@ -7,19 +7,19 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Tabs,
|
Tabs,
|
||||||
Tab,
|
|
||||||
Paper,
|
|
||||||
FormControl,
|
FormControl,
|
||||||
Select,
|
Select,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
Chip,
|
Chip,
|
||||||
Alert,
|
Alert,
|
||||||
Stack,
|
|
||||||
SelectChangeEvent,
|
SelectChangeEvent,
|
||||||
|
useTheme,
|
||||||
|
useMediaQuery,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import PrintIcon from '@mui/icons-material/Print';
|
import PrintIcon from '@mui/icons-material/Print';
|
||||||
import DifferenceIcon from '@mui/icons-material/Difference';
|
import DifferenceIcon from '@mui/icons-material/Difference';
|
||||||
|
import ForumIcon from '@mui/icons-material/Forum';
|
||||||
import {
|
import {
|
||||||
Save as SaveIcon,
|
Save as SaveIcon,
|
||||||
ModelTraining,
|
ModelTraining,
|
||||||
@ -42,10 +42,12 @@ import { Scrollable } from 'components/Scrollable';
|
|||||||
import * as Types from 'types/types';
|
import * as Types from 'types/types';
|
||||||
import { StreamingOptions } from 'services/api-client';
|
import { StreamingOptions } from 'services/api-client';
|
||||||
import { StatusBox, StatusIcon } from './StatusIcon';
|
import { StatusBox, StatusIcon } from './StatusIcon';
|
||||||
import { ResumeChat } from 'components/ResumeChat';
|
import { ResumeChat } from 'components/ui/ResumeChat';
|
||||||
import { DiffViewer } from 'components/DiffViewer';
|
import { DiffViewer } from 'components/DiffViewer';
|
||||||
import { ResumePreview, resumeStyles } from './ResumePreview';
|
import { ResumePreview, resumeStyles } from './ResumePreview';
|
||||||
import { useAppState } from 'hooks/GlobalContext';
|
import { useAppState } from 'hooks/GlobalContext';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { TabWithTooltip } from './TabWithTooltip';
|
||||||
|
|
||||||
interface ResumeRevision {
|
interface ResumeRevision {
|
||||||
revisionId: string;
|
revisionId: string;
|
||||||
@ -65,13 +67,17 @@ interface ResumeEditProps {
|
|||||||
const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
||||||
const { setSnack } = useAppState();
|
const { setSnack } = useAppState();
|
||||||
const { onClose, resumeId, onSave } = props;
|
const { onClose, resumeId, onSave } = props;
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
const isLarge = useMediaQuery(theme.breakpoints.up('md'));
|
||||||
|
const navigate = useNavigate();
|
||||||
const { selectedCandidate, selectedJob } = useAppState();
|
const { selectedCandidate, selectedJob } = useAppState();
|
||||||
const { apiClient } = useAuth();
|
const { apiClient } = useAuth();
|
||||||
const [editContent, setEditContent] = useState<string>('');
|
const [editContent, setEditContent] = useState<string>('');
|
||||||
|
|
||||||
const [saving, setSaving] = useState<boolean>(false);
|
const [saving, setSaving] = useState<boolean>(false);
|
||||||
const [tabValue, setTabValue] = useState('markdown');
|
const [leftColumn, setLeftColumn] = useState('markdown');
|
||||||
const [jobTabValue, setJobTabValue] = useState('chat');
|
const [rightColumn, setRightColumn] = useState('chat');
|
||||||
const [status, setStatus] = useState<string>('');
|
const [status, setStatus] = useState<string>('');
|
||||||
const [statusType, setStatusType] = useState<Types.ApiActivityType | null>(null);
|
const [statusType, setStatusType] = useState<Types.ApiActivityType | null>(null);
|
||||||
const [error, setError] = useState<Types.ChatMessageError | null>(null);
|
const [error, setError] = useState<Types.ChatMessageError | null>(null);
|
||||||
@ -79,6 +85,7 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
|||||||
const [resume, setResume] = useState<Resume | null>(null);
|
const [resume, setResume] = useState<Resume | null>(null);
|
||||||
const [isAIGenerated, setIsAIGenerated] = useState<boolean>(false);
|
const [isAIGenerated, setIsAIGenerated] = useState<boolean>(false);
|
||||||
const [editPrompt, setEditPrompt] = useState<string>('');
|
const [editPrompt, setEditPrompt] = useState<string>('');
|
||||||
|
const [columnView, setColumnView] = useState<'left' | 'right'>('left');
|
||||||
|
|
||||||
// Revision-related state
|
// Revision-related state
|
||||||
const [revisions, setRevisions] = useState<ResumeRevision[]>([]);
|
const [revisions, setRevisions] = useState<ResumeRevision[]>([]);
|
||||||
@ -325,7 +332,8 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
|||||||
return generatedResume;
|
return generatedResume;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTabChange = (event: React.SyntheticEvent, newValue: string): void => {
|
const handleLeftColumnChange = (event: React.SyntheticEvent, newValue: string): void => {
|
||||||
|
setColumnView('left');
|
||||||
if (newValue === 'print') {
|
if (newValue === 'print') {
|
||||||
console.log('Printing resume...');
|
console.log('Printing resume...');
|
||||||
reactToPrintFn();
|
reactToPrintFn();
|
||||||
@ -333,7 +341,7 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
|||||||
}
|
}
|
||||||
if (newValue === 'regenerate') {
|
if (newValue === 'regenerate') {
|
||||||
setSnack('Regenerating resume...');
|
setSnack('Regenerating resume...');
|
||||||
setTabValue('markdown');
|
setLeftColumn('markdown');
|
||||||
generateResume();
|
generateResume();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -341,14 +349,15 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
|||||||
setEditContent(current?.resume || '');
|
setEditContent(current?.resume || '');
|
||||||
setEditPrompt(current?.prompt || '');
|
setEditPrompt(current?.prompt || '');
|
||||||
setIsAIGenerated(current?.aiGenerated || false);
|
setIsAIGenerated(current?.aiGenerated || false);
|
||||||
setTabValue('markdown');
|
setLeftColumn('markdown');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setTabValue(newValue);
|
setLeftColumn(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleJobTabChange = (event: React.SyntheticEvent, newValue: string): void => {
|
const handleRightColumnChange = (event: React.SyntheticEvent, newValue: string): void => {
|
||||||
setJobTabValue(newValue);
|
setColumnView('right');
|
||||||
|
setRightColumn(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = (): void => {
|
const handleClose = (): void => {
|
||||||
@ -359,12 +368,10 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
|||||||
(selectedRevision !== 'current' && resume?.aiGenerated ? resume?.prompt : '') ||
|
(selectedRevision !== 'current' && resume?.aiGenerated ? resume?.prompt : '') ||
|
||||||
(isAIGenerated ? editPrompt : '') ||
|
(isAIGenerated ? editPrompt : '') ||
|
||||||
'Manual edits';
|
'Manual edits';
|
||||||
console.log(
|
|
||||||
`Change log: ${changeLog}, isAIGenerated: ${isAIGenerated}, editPrompt: ${editPrompt}, selectedRevision: ${selectedRevision}, resume?.aiGenerated: ${resume?.aiGenerated}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
|
className="ResumeEdit"
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
@ -375,6 +382,7 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
|
className="ResumeEditHeader"
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@ -384,7 +392,14 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 1 }}>
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: isMobile ? 'column' : 'row',
|
||||||
|
gap: 2,
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -392,7 +407,7 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
|||||||
gap: 0.5,
|
gap: 0.5,
|
||||||
m: 0,
|
m: 0,
|
||||||
p: 0,
|
p: 0,
|
||||||
minWidth: '25rem',
|
minWidth: '20rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{resume && (
|
{resume && (
|
||||||
@ -402,8 +417,21 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
|||||||
{resume.job?.title || 'No Job Title Assigned'},{' '}
|
{resume.job?.title || 'No Job Title Assigned'},{' '}
|
||||||
{resume.job?.company || 'No Company Assigned'}
|
{resume.job?.company || 'No Company Assigned'}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption" display="block" color="text.secondary">
|
<Typography
|
||||||
Resume ID: {resume.id}
|
variant="caption"
|
||||||
|
display="block"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ display: 'flex', flexDirection: 'row' }}
|
||||||
|
>
|
||||||
|
Resume ID:{' '}
|
||||||
|
<Box
|
||||||
|
sx={{ cursor: 'pointer', textDecoration: 'underline' }}
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/chat/${resume.id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{resume.id}
|
||||||
|
</Box>
|
||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -413,7 +441,7 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1, flexWrap: 'wrap' }}>
|
||||||
{/* Style Selector */}
|
{/* Style Selector */}
|
||||||
<FormControl size="small" sx={{ minWidth: 'min-content' }}>
|
<FormControl size="small" sx={{ minWidth: 'min-content' }}>
|
||||||
<InputLabel id="resume-style-label">Resume Style</InputLabel>
|
<InputLabel id="resume-style-label">Resume Style</InputLabel>
|
||||||
@ -444,87 +472,86 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
|||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'row' }}>
|
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 1 }}>
|
||||||
<Stack direction="row" spacing={2} alignItems="center">
|
<FormControl size="small" sx={{ minWidth: 200 }}>
|
||||||
<FormControl size="small" sx={{ minWidth: 200 }}>
|
<InputLabel id="revision-select-label">
|
||||||
<InputLabel id="revision-select-label">
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<HistoryIcon fontSize="small" />
|
||||||
|
Version History
|
||||||
|
</Box>
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="revision-select-label"
|
||||||
|
value={selectedRevision}
|
||||||
|
onChange={handleRevisionChange}
|
||||||
|
label="Version History"
|
||||||
|
disabled={loadingRevisions}
|
||||||
|
>
|
||||||
|
<MenuItem value="current">
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<HistoryIcon fontSize="small" />
|
<Chip label="CURRENT" size="small" color="primary" variant="outlined" />
|
||||||
Version History
|
<Typography variant="body2">Current Version</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</InputLabel>
|
</MenuItem>
|
||||||
<Select
|
{revisions.map(revision => (
|
||||||
labelId="revision-select-label"
|
<MenuItem key={revision.revisionId} value={revision.revisionId}>
|
||||||
value={selectedRevision}
|
<Box
|
||||||
onChange={handleRevisionChange}
|
sx={{
|
||||||
label="Version History"
|
display: 'flex',
|
||||||
disabled={loadingRevisions}
|
flexDirection: 'column',
|
||||||
>
|
alignItems: 'flex-start',
|
||||||
<MenuItem value="current">
|
}}
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
>
|
||||||
<Chip label="CURRENT" size="small" color="primary" variant="outlined" />
|
<Typography variant="body2">
|
||||||
<Typography variant="body2">Current Version</Typography>
|
{formatRevisionTimestamp(revision.revisionTimestamp)}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="caption" color="text.secondary">
|
||||||
|
Updated: {formatRevisionTimestamp(revision.updatedAt)}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ fontSize: '0.65rem' }}
|
||||||
|
>
|
||||||
|
ID: {revision.revisionId.substring(0, 8)}...
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{revisions.map(revision => (
|
))}
|
||||||
<MenuItem key={revision.revisionId} value={revision.revisionId}>
|
</Select>
|
||||||
<Box
|
</FormControl>
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="body2">
|
|
||||||
{formatRevisionTimestamp(revision.revisionTimestamp)}
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="caption" color="text.secondary">
|
|
||||||
Updated: {formatRevisionTimestamp(revision.updatedAt)}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
color="text.secondary"
|
|
||||||
sx={{ fontSize: '0.65rem' }}
|
|
||||||
>
|
|
||||||
ID: {revision.revisionId.substring(0, 8)}...
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<Tooltip title="Refresh Revisions">
|
<Tooltip title="Refresh Revisions">
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
onClick={loadResumeRevisions}
|
onClick={loadResumeRevisions}
|
||||||
disabled={loadingRevisions}
|
disabled={loadingRevisions}
|
||||||
>
|
>
|
||||||
<RefreshIcon />
|
<RefreshIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{selectedRevision !== 'current' && (
|
{selectedRevision !== 'current' && (
|
||||||
<>
|
<>
|
||||||
<Tooltip title="Restore this revision to editor">
|
<Tooltip title="Restore this revision to editor">
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
startIcon={<RestoreFromTrashIcon />}
|
startIcon={<RestoreFromTrashIcon />}
|
||||||
onClick={restoreRevision}
|
onClick={restoreRevision}
|
||||||
disabled={loadingRevision}
|
disabled={loadingRevision}
|
||||||
>
|
>
|
||||||
Restore
|
Restore
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
|
className="ResumeEditContent"
|
||||||
sx={{
|
sx={{
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -533,6 +560,99 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
|||||||
height: '100%',
|
height: '100%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Box className="ResumeEditTabs" sx={{ display: 'flex', m: 0, p: 0 }}>
|
||||||
|
<Tabs
|
||||||
|
value={isLarge ? leftColumn : columnView === 'left' ? leftColumn : undefined}
|
||||||
|
onChange={handleLeftColumnChange}
|
||||||
|
sx={{
|
||||||
|
'& .MuiTab-root': {
|
||||||
|
minWidth: 'fit-content',
|
||||||
|
width: 'fit-content',
|
||||||
|
fontSize: isLarge ? '0.6rem' : '0.5rem',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TabWithTooltip
|
||||||
|
tooltip="Markdown Editor"
|
||||||
|
value="markdown"
|
||||||
|
icon={<EditDocumentIcon />}
|
||||||
|
label={isLarge ? 'Markdown' : ''}
|
||||||
|
/>
|
||||||
|
<TabWithTooltip
|
||||||
|
tooltip="View Changes"
|
||||||
|
value="diff"
|
||||||
|
disabled={editContent === current?.resume}
|
||||||
|
icon={<DifferenceIcon />}
|
||||||
|
label={isLarge ? 'Changes' : undefined}
|
||||||
|
/>
|
||||||
|
<TabWithTooltip
|
||||||
|
tooltip="Preview Resume"
|
||||||
|
value="preview"
|
||||||
|
icon={<PreviewIcon />}
|
||||||
|
label={isLarge ? 'Preview' : undefined}
|
||||||
|
/>
|
||||||
|
<TabWithTooltip
|
||||||
|
tooltip="Print Resume"
|
||||||
|
disabled={leftColumn !== 'preview'}
|
||||||
|
value="print"
|
||||||
|
icon={<PrintIcon />}
|
||||||
|
label={isLarge ? 'Print' : undefined}
|
||||||
|
/>
|
||||||
|
<TabWithTooltip
|
||||||
|
tooltip="Regenerate Resume"
|
||||||
|
value="regenerate"
|
||||||
|
icon={<ModelTraining />}
|
||||||
|
label={isLarge ? 'Regenerate' : undefined}
|
||||||
|
/>
|
||||||
|
<TabWithTooltip
|
||||||
|
tooltip="Undo Changes"
|
||||||
|
value="undo"
|
||||||
|
disabled={editContent === resume?.resume}
|
||||||
|
icon={<UndoIcon />}
|
||||||
|
label={isLarge ? 'Revert' : undefined}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
<Tabs
|
||||||
|
value={isLarge ? rightColumn : columnView === 'right' ? rightColumn : undefined}
|
||||||
|
onChange={handleRightColumnChange}
|
||||||
|
sx={{
|
||||||
|
'& .MuiTab-root': {
|
||||||
|
minWidth: 'fit-content',
|
||||||
|
width: 'fit-content',
|
||||||
|
fontSize: isLarge ? '0.6rem' : '0.5rem',
|
||||||
|
},
|
||||||
|
borderLeft: isLarge ? '1px solid #ccc' : 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{resume && resume.job !== undefined && (
|
||||||
|
<TabWithTooltip
|
||||||
|
tooltip="Job"
|
||||||
|
value="job"
|
||||||
|
icon={<WorkIcon />}
|
||||||
|
label={isLarge ? 'Job' : ''}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<TabWithTooltip
|
||||||
|
tooltip="AI Edit"
|
||||||
|
value="chat"
|
||||||
|
icon={<ForumIcon />}
|
||||||
|
label={isLarge ? 'AI Edit' : ''}
|
||||||
|
/>
|
||||||
|
</Tabs>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{status && (
|
||||||
|
<Box sx={{ mt: 1, mb: 1, width: '100%' }}>
|
||||||
|
<StatusBox>
|
||||||
|
{statusType && <StatusIcon type={statusType} />}
|
||||||
|
<Typography variant="body2" sx={{ ml: 1 }}>
|
||||||
|
{status || 'Processing...'}
|
||||||
|
</Typography>
|
||||||
|
</StatusBox>
|
||||||
|
{status && !error && <LinearProgress sx={{ mt: 1 }} />}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -545,246 +665,213 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Paper
|
{(isLarge || columnView === 'left') && (
|
||||||
sx={{
|
|
||||||
flex: 1,
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
position: 'relative',
|
|
||||||
maxWidth: '100%',
|
|
||||||
height: '100%',
|
|
||||||
overflow: 'hidden',
|
|
||||||
alignItems: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ display: 'flex', m: 0, p: 0 }}>
|
|
||||||
<Tabs value={tabValue} onChange={handleTabChange}>
|
|
||||||
<Tab value="markdown" icon={<EditDocumentIcon />} label="Markdown" />
|
|
||||||
<Tab
|
|
||||||
value="diff"
|
|
||||||
disabled={editContent === current?.resume}
|
|
||||||
icon={<DifferenceIcon />}
|
|
||||||
label="Changes"
|
|
||||||
/>
|
|
||||||
<Tab value="preview" icon={<PreviewIcon />} label="Preview" />
|
|
||||||
<Tab
|
|
||||||
disabled={tabValue !== 'preview'}
|
|
||||||
value="print"
|
|
||||||
icon={<PrintIcon />}
|
|
||||||
label="Print"
|
|
||||||
/>
|
|
||||||
<Tab value="regenerate" icon={<ModelTraining />} label="Regenerate" />
|
|
||||||
<Tab
|
|
||||||
value="undo"
|
|
||||||
disabled={editContent === resume?.resume}
|
|
||||||
icon={<UndoIcon />}
|
|
||||||
label="Revert"
|
|
||||||
/>
|
|
||||||
</Tabs>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{status && (
|
|
||||||
<Box sx={{ mt: 1, mb: 1, width: '100%' }}>
|
|
||||||
<StatusBox>
|
|
||||||
{statusType && <StatusIcon type={statusType} />}
|
|
||||||
<Typography variant="body2" sx={{ ml: 1 }}>
|
|
||||||
{status || 'Processing...'}
|
|
||||||
</Typography>
|
|
||||||
</StatusBox>
|
|
||||||
{status && !error && <LinearProgress sx={{ mt: 1 }} />}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
|
||||||
flexGrow: 1,
|
|
||||||
width: '100%',
|
|
||||||
position: 'relative',
|
|
||||||
flexDirection: 'column',
|
|
||||||
m: 0,
|
|
||||||
p: 0,
|
|
||||||
gap: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{selectedRevision !== 'current' && (
|
|
||||||
<Alert severity="info" sx={{ width: '100%' }}>
|
|
||||||
You are viewing a previous version. Click "Restore" to load this content
|
|
||||||
into the editor.
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isAIGenerated && (
|
|
||||||
<Alert severity="warning" sx={{ width: '100%' }}>
|
|
||||||
This resume was generated by AI and has not been manually edited. Review and then
|
|
||||||
selecte 'Save'.
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Scrollable
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
minHeight: 0,
|
|
||||||
'& > *:not(.Scrollable)': {
|
|
||||||
flexShrink: 0,
|
|
||||||
},
|
|
||||||
position: 'relative',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tabValue === 'markdown' && (
|
|
||||||
<>
|
|
||||||
{selectedRevision === 'current' ? (
|
|
||||||
<BackstoryTextField
|
|
||||||
value={editContent}
|
|
||||||
onChange={(value): void => setEditContent(value)}
|
|
||||||
style={{
|
|
||||||
position: 'relative',
|
|
||||||
maxHeight: '100%',
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
minHeight: '100%',
|
|
||||||
flexGrow: 1,
|
|
||||||
flex: 1,
|
|
||||||
overflowY: 'auto',
|
|
||||||
fontFamily: 'monospace',
|
|
||||||
backgroundColor: '#fafafa',
|
|
||||||
fontSize: '12px',
|
|
||||||
}}
|
|
||||||
placeholder="Enter resume content..."
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
p: 2,
|
|
||||||
border: '1px solid',
|
|
||||||
borderColor: 'divider',
|
|
||||||
borderRadius: 1,
|
|
||||||
backgroundColor: 'grey.50',
|
|
||||||
height: '100%',
|
|
||||||
overflow: 'auto',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{loadingRevision ? (
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
||||||
<LinearProgress sx={{ flexGrow: 1 }} />
|
|
||||||
<Typography variant="body2">Loading revision...</Typography>
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
<pre style={{ border: 0 }}>{resume?.resume}</pre>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{tabValue === 'diff' && current && (
|
|
||||||
<DiffViewer
|
|
||||||
changeLog={changeLog}
|
|
||||||
original={{ content: current.resume || '', name: 'original' }}
|
|
||||||
modified={{
|
|
||||||
content: selectedRevision !== 'current' && resume ? resume.resume : editContent,
|
|
||||||
name: 'modified',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{tabValue === 'preview' && resume && resume.candidate && (
|
|
||||||
<Box
|
|
||||||
className="document-container"
|
|
||||||
ref={printContentRef}
|
|
||||||
sx={currentStyle.contentStyle}
|
|
||||||
>
|
|
||||||
<ResumePreview resume={resume} selectedStyle={selectedStyle} />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Scrollable>
|
|
||||||
</Paper>
|
|
||||||
<Scrollable
|
|
||||||
sx={{
|
|
||||||
flex: 1,
|
|
||||||
display: 'flex',
|
|
||||||
height: '100%',
|
|
||||||
overflowY: 'auto',
|
|
||||||
position: 'relative',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Paper
|
|
||||||
sx={{
|
|
||||||
p: 1,
|
|
||||||
flex: 1,
|
flex: 1,
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
|
maxWidth: '100%',
|
||||||
|
height: '100%',
|
||||||
|
overflow: 'hidden',
|
||||||
|
alignItems: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tabs value={jobTabValue} onChange={handleJobTabChange}>
|
<Box
|
||||||
{resume && resume.job !== undefined && (
|
sx={{
|
||||||
<Tab value="job" icon={<WorkIcon />} label="Job" />
|
display: 'flex',
|
||||||
|
flexGrow: 1,
|
||||||
|
width: '100%',
|
||||||
|
position: 'relative',
|
||||||
|
flexDirection: 'column',
|
||||||
|
m: 0,
|
||||||
|
p: 0,
|
||||||
|
gap: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{selectedRevision !== 'current' && (
|
||||||
|
<Alert severity="info" sx={{ width: '100%' }}>
|
||||||
|
You are viewing a previous version. Click "Restore" to load this
|
||||||
|
content into the editor.
|
||||||
|
</Alert>
|
||||||
)}
|
)}
|
||||||
<Tab value="chat" icon={<ModelTraining />} label="AI Edit" />
|
|
||||||
</Tabs>
|
|
||||||
|
|
||||||
{resume && resume.job !== undefined && jobTabValue === 'job' && (
|
{isAIGenerated && (
|
||||||
<JobInfo
|
<Alert severity="warning" sx={{ width: '100%' }}>
|
||||||
variant={'all'}
|
This resume was generated by AI and has not been manually edited. Review and
|
||||||
job={resume.job}
|
then selecte 'Save'.
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Scrollable
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
minHeight: 0,
|
||||||
|
'& > *:not(.Scrollable)': {
|
||||||
|
flexShrink: 0,
|
||||||
|
},
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{leftColumn === 'markdown' && (
|
||||||
|
<>
|
||||||
|
{selectedRevision === 'current' ? (
|
||||||
|
<BackstoryTextField
|
||||||
|
value={editContent}
|
||||||
|
onChange={(value): void => setEditContent(value)}
|
||||||
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
maxHeight: '100%',
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
minHeight: '100%',
|
||||||
|
flexGrow: 1,
|
||||||
|
flex: 1,
|
||||||
|
overflowY: 'auto',
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
fontSize: '12px',
|
||||||
|
}}
|
||||||
|
placeholder="Enter resume content..."
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: 2,
|
||||||
|
border: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
borderRadius: 1,
|
||||||
|
backgroundColor: 'grey.50',
|
||||||
|
height: '100%',
|
||||||
|
overflow: 'auto',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loadingRevision ? (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<LinearProgress sx={{ flexGrow: 1 }} />
|
||||||
|
<Typography variant="body2">Loading revision...</Typography>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<pre style={{ border: 0 }}>{resume?.resume}</pre>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{leftColumn === 'diff' && current && (
|
||||||
|
<DiffViewer
|
||||||
|
changeLog={changeLog}
|
||||||
|
original={{ content: current.resume || '', name: 'original' }}
|
||||||
|
modified={{
|
||||||
|
content:
|
||||||
|
selectedRevision !== 'current' && resume ? resume.resume : editContent,
|
||||||
|
name: 'modified',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{leftColumn === 'preview' && resume && resume.candidate && (
|
||||||
|
<Box
|
||||||
|
className="document-container"
|
||||||
|
ref={printContentRef}
|
||||||
|
sx={currentStyle.contentStyle}
|
||||||
|
>
|
||||||
|
<ResumePreview resume={resume} selectedStyle={selectedStyle} />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Scrollable>
|
||||||
|
{resume && (
|
||||||
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
m: 0,
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
gap: 1,
|
||||||
|
alignItems: 'center',
|
||||||
p: 1,
|
p: 1,
|
||||||
backgroundColor: '#f8f0e0',
|
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
|
{onClose && <Button onClick={handleClose}>Cancel</Button>}
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
handleSave();
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
disabled={saveDisabled}
|
||||||
|
startIcon={<SaveIcon />}
|
||||||
|
>
|
||||||
|
{saving ? 'Saving...' : 'Save'}
|
||||||
|
</Button>
|
||||||
|
<Typography variant="caption" display="block" color="text.secondary">
|
||||||
|
Last saved:{' '}
|
||||||
|
{resume.updatedAt ? new Date(resume.updatedAt).toLocaleString() : 'N/A'}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
{jobTabValue === 'chat' && resume && (
|
</Box>
|
||||||
<ResumeChat
|
)}
|
||||||
session={resume.id || ''}
|
{(isLarge || columnView === 'right') && (
|
||||||
resume={editContent}
|
<Box
|
||||||
onResumeChange={(prompt: string, newResume: string): void => {
|
sx={{
|
||||||
console.log('onResumeChange:', prompt);
|
display: 'flex',
|
||||||
if (newResume !== editContent) {
|
flexDirection: 'column',
|
||||||
setBackupContent(editContent);
|
flex: 1,
|
||||||
setEditPrompt(prompt);
|
position: 'relative',
|
||||||
setEditContent(newResume);
|
width: '100%',
|
||||||
setIsAIGenerated(true);
|
}}
|
||||||
}
|
>
|
||||||
}}
|
<Scrollable
|
||||||
sx={{
|
sx={{
|
||||||
m: 1,
|
flex: 1,
|
||||||
p: 1,
|
display: 'flex',
|
||||||
flexGrow: 1,
|
height: '100%',
|
||||||
position: 'relative',
|
width: '100%',
|
||||||
maxWidth: 'fit-content',
|
overflowY: 'auto',
|
||||||
minWidth: '100%',
|
position: 'relative',
|
||||||
}}
|
p: 1,
|
||||||
/>
|
flexDirection: 'column',
|
||||||
)}
|
overflow: 'hidden',
|
||||||
</Paper>
|
}}
|
||||||
</Scrollable>
|
>
|
||||||
|
{resume && rightColumn === 'job' && (
|
||||||
|
<>
|
||||||
|
{resume.job !== undefined ? (
|
||||||
|
<JobInfo variant={'all'} job={resume.job} />
|
||||||
|
) : (
|
||||||
|
<Box>No matching job found.</Box>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{rightColumn === 'chat' && resume && (
|
||||||
|
<ResumeChat
|
||||||
|
session={resume.id || ''}
|
||||||
|
resume={editContent}
|
||||||
|
onResumeChange={(prompt: string, newResume: string): void => {
|
||||||
|
console.log('onResumeChange:', prompt);
|
||||||
|
if (newResume !== editContent) {
|
||||||
|
setBackupContent(editContent);
|
||||||
|
setEditPrompt(prompt);
|
||||||
|
setEditContent(newResume);
|
||||||
|
setIsAIGenerated(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
m: 1,
|
||||||
|
p: 1,
|
||||||
|
flexGrow: 1,
|
||||||
|
position: 'relative',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Scrollable>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
{resume && (
|
|
||||||
<Box
|
|
||||||
sx={{ display: 'flex', flexDirection: 'row', gap: 1, alignItems: 'center', pl: 1, pt: 1 }}
|
|
||||||
>
|
|
||||||
{onClose && <Button onClick={handleClose}>Cancel</Button>}
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
handleSave();
|
|
||||||
}}
|
|
||||||
variant="contained"
|
|
||||||
disabled={saveDisabled}
|
|
||||||
startIcon={<SaveIcon />}
|
|
||||||
>
|
|
||||||
{saving ? 'Saving...' : 'Save'}
|
|
||||||
</Button>
|
|
||||||
<Typography variant="caption" display="block" color="text.secondary">
|
|
||||||
Last saved: {resume.updatedAt ? new Date(resume.updatedAt).toLocaleString() : 'N/A'}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -528,9 +528,9 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
|
|||||||
if (mode === 'edit' && selectedResume) {
|
if (mode === 'edit' && selectedResume) {
|
||||||
return (
|
return (
|
||||||
<ResumeEdit
|
<ResumeEdit
|
||||||
onClose={() => {
|
// onClose={() => {
|
||||||
navigate(window.location.pathname.replace('/edit', ''));
|
// navigate(window.location.pathname.replace('/edit', ''));
|
||||||
}}
|
// }}
|
||||||
resumeId={selectedResume.id}
|
resumeId={selectedResume.id}
|
||||||
onSave={handleSave}
|
onSave={handleSave}
|
||||||
/>
|
/>
|
||||||
|
44
frontend/src/components/ui/TabWithTooltip.tsx
Normal file
44
frontend/src/components/ui/TabWithTooltip.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Tab, Tooltip, TabProps } from '@mui/material';
|
||||||
|
|
||||||
|
interface TabWithTooltipProps extends TabProps {
|
||||||
|
tooltip: string;
|
||||||
|
showTooltip?: boolean; // Optional prop to control when tooltip shows
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabWithTooltip: React.FC<TabWithTooltipProps> = ({
|
||||||
|
tooltip,
|
||||||
|
showTooltip = true,
|
||||||
|
disabled = false,
|
||||||
|
children,
|
||||||
|
...tabProps
|
||||||
|
}) => {
|
||||||
|
const tabElement = (
|
||||||
|
<Tab disabled={disabled} {...tabProps}>
|
||||||
|
{children}
|
||||||
|
</Tab>
|
||||||
|
);
|
||||||
|
|
||||||
|
// Don't show tooltip if showTooltip is false
|
||||||
|
if (!showTooltip) {
|
||||||
|
return tabElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For disabled tabs, wrap in span since Tooltip doesn't work on disabled elements
|
||||||
|
if (disabled) {
|
||||||
|
return (
|
||||||
|
<Tooltip title={tooltip} placement="bottom">
|
||||||
|
<span style={{ display: 'inline-block' }}>{tabElement}</span>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal case with tooltip
|
||||||
|
return (
|
||||||
|
<Tooltip title={tooltip} placement="bottom">
|
||||||
|
{tabElement}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { TabWithTooltip };
|
Loading…
x
Reference in New Issue
Block a user