Lots of mobile and desktop edit view tweaks

This commit is contained in:
James Ketr 2025-07-19 14:23:28 -07:00
parent 97272d9175
commit 064868e96e
6 changed files with 505 additions and 350 deletions

View File

@ -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>
); );
} }
); );

View File

@ -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 }}>

View File

@ -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);

View File

@ -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,8 +472,7 @@ 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 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
@ -518,13 +545,13 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
</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,52 +560,83 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
height: '100%', height: '100%',
}} }}
> >
<Box <Box className="ResumeEditTabs" sx={{ display: 'flex', m: 0, p: 0 }}>
<Tabs
value={isLarge ? leftColumn : columnView === 'left' ? leftColumn : undefined}
onChange={handleLeftColumnChange}
sx={{ sx={{
display: 'flex', '& .MuiTab-root': {
flexDirection: 'row', minWidth: 'fit-content',
height: '100%', width: 'fit-content',
gap: 1, fontSize: isLarge ? '0.6rem' : '0.5rem',
pt: 1, },
width: '100%',
position: 'relative',
overflow: 'hidden',
}} }}
> >
<Paper <TabWithTooltip
sx={{ tooltip="Markdown Editor"
flex: 1, value="markdown"
display: 'flex', icon={<EditDocumentIcon />}
flexDirection: 'column', label={isLarge ? 'Markdown' : ''}
position: 'relative', />
maxWidth: '100%', <TabWithTooltip
height: '100%', tooltip="View Changes"
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" value="diff"
disabled={editContent === current?.resume} disabled={editContent === current?.resume}
icon={<DifferenceIcon />} icon={<DifferenceIcon />}
label="Changes" label={isLarge ? 'Changes' : undefined}
/> />
<Tab value="preview" icon={<PreviewIcon />} label="Preview" /> <TabWithTooltip
<Tab tooltip="Preview Resume"
disabled={tabValue !== 'preview'} value="preview"
icon={<PreviewIcon />}
label={isLarge ? 'Preview' : undefined}
/>
<TabWithTooltip
tooltip="Print Resume"
disabled={leftColumn !== 'preview'}
value="print" value="print"
icon={<PrintIcon />} icon={<PrintIcon />}
label="Print" label={isLarge ? 'Print' : undefined}
/> />
<Tab value="regenerate" icon={<ModelTraining />} label="Regenerate" /> <TabWithTooltip
<Tab tooltip="Regenerate Resume"
value="regenerate"
icon={<ModelTraining />}
label={isLarge ? 'Regenerate' : undefined}
/>
<TabWithTooltip
tooltip="Undo Changes"
value="undo" value="undo"
disabled={editContent === resume?.resume} disabled={editContent === resume?.resume}
icon={<UndoIcon />} icon={<UndoIcon />}
label="Revert" 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> </Tabs>
</Box> </Box>
@ -595,6 +653,31 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
</Box> </Box>
)} )}
<Box
sx={{
display: 'flex',
flexDirection: 'row',
height: '100%',
gap: 1,
pt: 1,
width: '100%',
position: 'relative',
overflow: 'hidden',
}}
>
{(isLarge || columnView === 'left') && (
<Box
sx={{
flex: 1,
display: 'flex',
flexDirection: 'column',
position: 'relative',
maxWidth: '100%',
height: '100%',
overflow: 'hidden',
alignItems: 'center',
}}
>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -609,15 +692,15 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
> >
{selectedRevision !== 'current' && ( {selectedRevision !== 'current' && (
<Alert severity="info" sx={{ width: '100%' }}> <Alert severity="info" sx={{ width: '100%' }}>
You are viewing a previous version. Click &quot;Restore&quot; to load this content You are viewing a previous version. Click &quot;Restore&quot; to load this
into the editor. content into the editor.
</Alert> </Alert>
)} )}
{isAIGenerated && ( {isAIGenerated && (
<Alert severity="warning" sx={{ width: '100%' }}> <Alert severity="warning" sx={{ width: '100%' }}>
This resume was generated by AI and has not been manually edited. Review and then This resume was generated by AI and has not been manually edited. Review and
selecte &apos;Save&apos;. then selecte &apos;Save&apos;.
</Alert> </Alert>
)} )}
</Box> </Box>
@ -635,7 +718,7 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
position: 'relative', position: 'relative',
}} }}
> >
{tabValue === 'markdown' && ( {leftColumn === 'markdown' && (
<> <>
{selectedRevision === 'current' ? ( {selectedRevision === 'current' ? (
<BackstoryTextField <BackstoryTextField
@ -681,17 +764,18 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
)} )}
</> </>
)} )}
{tabValue === 'diff' && current && ( {leftColumn === 'diff' && current && (
<DiffViewer <DiffViewer
changeLog={changeLog} changeLog={changeLog}
original={{ content: current.resume || '', name: 'original' }} original={{ content: current.resume || '', name: 'original' }}
modified={{ modified={{
content: selectedRevision !== 'current' && resume ? resume.resume : editContent, content:
selectedRevision !== 'current' && resume ? resume.resume : editContent,
name: 'modified', name: 'modified',
}} }}
/> />
)} )}
{tabValue === 'preview' && resume && resume.candidate && ( {leftColumn === 'preview' && resume && resume.candidate && (
<Box <Box
className="document-container" className="document-container"
ref={printContentRef} ref={printContentRef}
@ -701,44 +785,68 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
</Box> </Box>
)} )}
</Scrollable> </Scrollable>
</Paper> {resume && (
<Box
sx={{
display: 'flex',
flexDirection: 'row',
gap: 1,
alignItems: 'center',
p: 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>
)}
{(isLarge || columnView === 'right') && (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
flex: 1,
position: 'relative',
width: '100%',
}}
>
<Scrollable <Scrollable
sx={{ sx={{
flex: 1, flex: 1,
display: 'flex', display: 'flex',
height: '100%', height: '100%',
width: '100%',
overflowY: 'auto', overflowY: 'auto',
position: 'relative', position: 'relative',
}}
>
<Paper
sx={{
p: 1, p: 1,
flex: 1,
display: 'flex',
flexDirection: 'column', flexDirection: 'column',
position: 'relative', overflow: 'hidden',
}} }}
> >
<Tabs value={jobTabValue} onChange={handleJobTabChange}> {resume && rightColumn === 'job' && (
{resume && resume.job !== undefined && ( <>
<Tab value="job" icon={<WorkIcon />} label="Job" /> {resume.job !== undefined ? (
<JobInfo variant={'all'} job={resume.job} />
) : (
<Box>No matching job found.</Box>
)} )}
<Tab value="chat" icon={<ModelTraining />} label="AI Edit" /> </>
</Tabs>
{resume && resume.job !== undefined && jobTabValue === 'job' && (
<JobInfo
variant={'all'}
job={resume.job}
sx={{
m: 0,
p: 1,
backgroundColor: '#f8f0e0',
}}
/>
)} )}
{jobTabValue === 'chat' && resume && ( {rightColumn === 'chat' && resume && (
<ResumeChat <ResumeChat
session={resume.id || ''} session={resume.id || ''}
resume={editContent} resume={editContent}
@ -756,36 +864,15 @@ const ResumeEdit: React.FC<ResumeEditProps> = (props: ResumeEditProps) => {
p: 1, p: 1,
flexGrow: 1, flexGrow: 1,
position: 'relative', position: 'relative',
maxWidth: 'fit-content',
minWidth: '100%',
}} }}
/> />
)} )}
</Paper>
</Scrollable> </Scrollable>
</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>
</Box>
</Box>
); );
}; };

View File

@ -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}
/> />

View 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 };