Improve CV parsing and profile editor flow
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import { Alert, Avatar, Box, Button, Chip, Divider, FormControl, InputLabel, LinearProgress, MenuItem, Paper, Select, TextField, Typography } from "@mui/material";
|
||||
import { Accordion, AccordionDetails, AccordionSummary, Alert, Avatar, Box, Button, Chip, Divider, FormControl, InputLabel, LinearProgress, MenuItem, Paper, Select, TextField, Typography } from "@mui/material";
|
||||
|
||||
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import PhotoCameraOutlinedIcon from "@mui/icons-material/PhotoCameraOutlined";
|
||||
|
||||
import { api } from "../api";
|
||||
@@ -399,22 +400,40 @@ export default function ProfilePage() {
|
||||
>
|
||||
{reprocessingCv ? t("profileCvReprocessing") : t("profileCvReprocess")}
|
||||
</Button>
|
||||
<Button variant="text" disabled={!profileCvText.trim()} onClick={() => navigator.clipboard.writeText(profileCvText)}>
|
||||
{t("profileCopyCvText")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
{uploadingCv ? <LinearProgress sx={{ mb: 1.5 }} /> : null}
|
||||
<TextField
|
||||
label={t("profileCvTextLabel")}
|
||||
value={profileCvText}
|
||||
onChange={(e) => setProfileCvText(e.target.value)}
|
||||
helperText={t("profileCvTextHelp")}
|
||||
multiline
|
||||
minRows={12}
|
||||
disabled={!isLocal}
|
||||
fullWidth
|
||||
/>
|
||||
<Alert severity="info" sx={{ mb: 2, borderRadius: 2.5 }}>
|
||||
{t("profileCvStructuredDefaultHint")}
|
||||
</Alert>
|
||||
<Accordion disableGutters elevation={0} sx={{ mb: 2, borderRadius: 3, border: "1px solid", borderColor: "divider", backgroundColor: "background.paper", "&:before": { display: "none" } }}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", gap: 1.5, alignItems: "center", width: "100%", pr: 1 }}>
|
||||
<Box>
|
||||
<Typography variant="subtitle1" sx={{ fontWeight: 800 }}>{t("profileCvRawPanelTitle")}</Typography>
|
||||
<Typography variant="body2" sx={{ color: "text.secondary" }}>{t("profileCvRawPanelHelp")}</Typography>
|
||||
</Box>
|
||||
<Chip size="small" label={t("profileCvSectionWordCount", { count: cvWordCount })} />
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<TextField
|
||||
label={t("profileCvTextLabel")}
|
||||
value={profileCvText}
|
||||
onChange={(e) => setProfileCvText(e.target.value)}
|
||||
helperText={t("profileCvTextHelp")}
|
||||
multiline
|
||||
minRows={12}
|
||||
disabled={!isLocal}
|
||||
fullWidth
|
||||
/>
|
||||
<Box sx={{ mt: 1.5, display: "flex", justifyContent: "flex-end" }}>
|
||||
<Button variant="text" disabled={!profileCvText.trim()} onClick={() => navigator.clipboard.writeText(profileCvText)}>
|
||||
{t("profileCopyCvText")}
|
||||
</Button>
|
||||
</Box>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
<Box sx={{ mt: 2, p: 1.5, borderRadius: 3, border: "1px solid", borderColor: "divider", backgroundColor: "background.paper" }}>
|
||||
<Box sx={{ display: "flex", justifyContent: "space-between", gap: 2, flexWrap: "wrap", alignItems: "center", mb: 1.5 }}>
|
||||
<Box>
|
||||
|
||||
@@ -147,10 +147,17 @@ test('profile page loads persisted structured cv and can re-parse it', async ()
|
||||
expect(screen.getByText(/extraction history/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/resume.pdf/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/current run/i)).toBeInTheDocument();
|
||||
expect(screen.getAllByText(/original extraction/i).length).toBeGreaterThan(0);
|
||||
const originalExtractionToggle = screen.getByRole('button', { name: /original extraction/i });
|
||||
expect(originalExtractionToggle).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(screen.getAllByText(/professional summary/i).length).toBeGreaterThan(0);
|
||||
expect(screen.getByLabelText(/full name/i)).toHaveValue('Demo User');
|
||||
expect(screen.getByText(/high 92%/i)).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(originalExtractionToggle);
|
||||
expect(originalExtractionToggle).toHaveAttribute('aria-expanded', 'true');
|
||||
expect(await screen.findByLabelText(/profile cv \/ master resume text/i)).toHaveValue('Professional Summary\nBuilt backend systems');
|
||||
|
||||
const analyzeButton = screen.getByRole('button', { name: /analyze sections/i });
|
||||
await waitFor(() => expect(analyzeButton).toBeEnabled());
|
||||
fireEvent.click(analyzeButton);
|
||||
|
||||
Reference in New Issue
Block a user