Added some mobile view work
This commit is contained in:
parent
ed69096ef0
commit
a5bf96437e
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Box, Typography, SxProps, Theme } from '@mui/material';
|
import { Box, Typography, SxProps, Theme, useMediaQuery, useTheme } from '@mui/material';
|
||||||
import {
|
import {
|
||||||
Email as EmailIcon,
|
Email as EmailIcon,
|
||||||
Phone as PhoneIcon,
|
Phone as PhoneIcon,
|
||||||
@ -305,9 +305,10 @@ export const resumeStyles: Record<string, ResumeStyle> = generateResumeStyles();
|
|||||||
interface StyledHeaderProps {
|
interface StyledHeaderProps {
|
||||||
candidate: Types.Candidate;
|
candidate: Types.Candidate;
|
||||||
style: ResumeStyle;
|
style: ResumeStyle;
|
||||||
|
isMobile: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledHeader: React.FC<StyledHeaderProps> = ({ candidate, style }) => {
|
const StyledHeader: React.FC<StyledHeaderProps> = ({ candidate, style, isMobile }) => {
|
||||||
const phone = parsePhoneNumberFromString(candidate.phone || '', 'US');
|
const phone = parsePhoneNumberFromString(candidate.phone || '', 'US');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -329,7 +330,7 @@ const StyledHeader: React.FC<StyledHeaderProps> = ({ candidate, style }) => {
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: isMobile ? 'column' : 'row',
|
||||||
alignItems: 'flex-start',
|
alignItems: 'flex-start',
|
||||||
gap: 1,
|
gap: 1,
|
||||||
}}
|
}}
|
||||||
@ -426,12 +427,12 @@ const StyledHeader: React.FC<StyledHeaderProps> = ({ candidate, style }) => {
|
|||||||
|
|
||||||
// Styled Footer Component
|
// Styled Footer Component
|
||||||
interface StyledFooterProps {
|
interface StyledFooterProps {
|
||||||
candidate: Types.Candidate;
|
resume: Types.Resume;
|
||||||
job?: Types.Job;
|
|
||||||
style: ResumeStyle;
|
style: ResumeStyle;
|
||||||
|
isMobile: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StyledFooter: React.FC<StyledFooterProps> = ({ candidate, job, style }) => {
|
const StyledFooter: React.FC<StyledFooterProps> = ({ resume, style, isMobile }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
@ -439,19 +440,18 @@ const StyledFooter: React.FC<StyledFooterProps> = ({ candidate, job, style }) =>
|
|||||||
sx={{
|
sx={{
|
||||||
...style.footerStyle,
|
...style.footerStyle,
|
||||||
color: style.color.secondary,
|
color: style.color.secondary,
|
||||||
|
fontSize: isMobile ? '0.6rem' : '1rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Dive deeper into my qualifications at Backstory...
|
Dive deeper into my qualifications at Backstory...
|
||||||
<Box
|
<Box
|
||||||
component="img"
|
component="img"
|
||||||
src={`/api/1.0/candidates/qr-code/${candidate.id || ''}/${(job && job.id) || ''}`}
|
src={`/api/1.0/candidates/qr-code/${resume.id}`}
|
||||||
alt="QR Code"
|
alt="QR Code"
|
||||||
className="qr-code"
|
className="qr-code"
|
||||||
sx={{ display: 'flex', mt: 1, mb: 1 }}
|
sx={{ display: 'flex', mt: 1, mb: 1 }}
|
||||||
/>
|
/>
|
||||||
{candidate?.username
|
{window.location.protocol}://{window.location.host}/chat/{resume.id}
|
||||||
? `${window.location.protocol}://${window.location.host}/u/${candidate?.username}`
|
|
||||||
: 'backstory'}
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ pb: 2 }}> </Box>
|
<Box sx={{ pb: 2 }}> </Box>
|
||||||
</>
|
</>
|
||||||
@ -470,6 +470,8 @@ export const ResumePreview: React.FC<ResumePreviewProps> = (props: ResumePreview
|
|||||||
const currentStyle = resumeStyles[selectedStyle] || resumeStyles.corporate;
|
const currentStyle = resumeStyles[selectedStyle] || resumeStyles.corporate;
|
||||||
const job: Types.Job | null = resume.job || null;
|
const job: Types.Job | null = resume.job || null;
|
||||||
const candidate: Types.Candidate | null = resume.candidate || null;
|
const candidate: Types.Candidate | null = resume.candidate || null;
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
|
||||||
if (!resume || !candidate || !job) {
|
if (!resume || !candidate || !job) {
|
||||||
return (
|
return (
|
||||||
@ -505,7 +507,7 @@ export const ResumePreview: React.FC<ResumePreviewProps> = (props: ResumePreview
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Custom Header */}
|
{/* Custom Header */}
|
||||||
<StyledHeader candidate={candidate} style={currentStyle} />
|
<StyledHeader isMobile={isMobile} candidate={candidate} style={currentStyle} />
|
||||||
|
|
||||||
{/* Styled Markdown Content */}
|
{/* Styled Markdown Content */}
|
||||||
<Box sx={currentStyle.markdownStyle}>
|
<Box sx={currentStyle.markdownStyle}>
|
||||||
@ -523,7 +525,7 @@ export const ResumePreview: React.FC<ResumePreviewProps> = (props: ResumePreview
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* QR Code Footer */}
|
{/* QR Code Footer */}
|
||||||
{job && <StyledFooter candidate={candidate} job={job} style={currentStyle} />}
|
{job && <StyledFooter isMobile={isMobile} resume={resume} style={currentStyle} />}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -341,6 +341,7 @@ const CandidateChatPage = forwardRef<ConversationHandle, CandidateChatPageProps>
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
p: 1,
|
p: 1,
|
||||||
|
fontSize: isMobile ? '0.8rem' : '1rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<strong>{resume.job?.title}</strong> at
|
<strong>{resume.job?.title}</strong> at
|
||||||
|
@ -58,6 +58,7 @@ from models import (
|
|||||||
RAGDocumentRequest,
|
RAGDocumentRequest,
|
||||||
RagContentMetadata,
|
RagContentMetadata,
|
||||||
RagContentResponse,
|
RagContentResponse,
|
||||||
|
Resume,
|
||||||
SkillAssessment,
|
SkillAssessment,
|
||||||
SkillMatchRequest,
|
SkillMatchRequest,
|
||||||
SkillStrength,
|
SkillStrength,
|
||||||
@ -716,55 +717,36 @@ async def get_candidate_profile_image(
|
|||||||
status_code=500, content=create_error_response("FETCH_ERROR", "Failed to retrieve profile image")
|
status_code=500, content=create_error_response("FETCH_ERROR", "Failed to retrieve profile image")
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.get("/qr-code/{candidate_id}/{job_id}")
|
@router.get("/qr-code/{resume_id}")
|
||||||
async def get_candidate_qr_code(
|
async def get_candidate_qr_code(
|
||||||
candidate_id: str = Path(..., description="ID of the candidate"),
|
resume_id: str = Path(..., description="ID of the resume"),
|
||||||
job_id: Optional[str] = Path(..., description="ID of the candidate"),
|
|
||||||
# current_user = Depends(get_current_user),
|
# current_user = Depends(get_current_user),
|
||||||
database: RedisDatabase = Depends(get_database),
|
database: RedisDatabase = Depends(get_database),
|
||||||
):
|
):
|
||||||
"""Get profile image of a candidate by username"""
|
"""Get profile image of a candidate by username"""
|
||||||
try:
|
try:
|
||||||
all_candidates_data = await database.get_all_candidates()
|
resume_data = await database.get_resume(resume_id=resume_id)
|
||||||
candidates_list = [Candidate.model_validate(data) for data in all_candidates_data.values()]
|
if not resume_data:
|
||||||
|
logger.warning(f"⚠️ Resume not found for ID: {resume_id}")
|
||||||
# Normalize username to lowercase for case-insensitive search
|
return JSONResponse(status_code=404, content=create_error_response("NOT_FOUND", "Resume not found"))
|
||||||
query_lower = candidate_id.lower()
|
resume: Resume = Resume.model_validate(resume_data)
|
||||||
|
candidate_data = await database.get_candidate(resume.candidate_id)
|
||||||
# Filter by search query
|
if not candidate_data:
|
||||||
candidates_list = [c for c in candidates_list if query_lower == c.id.lower()]
|
logger.warning(f"⚠️ Candidate not found for resume ID: {resume_id}")
|
||||||
|
|
||||||
if not len(candidates_list):
|
|
||||||
return JSONResponse(status_code=404, content=create_error_response("NOT_FOUND", "Candidate not found"))
|
return JSONResponse(status_code=404, content=create_error_response("NOT_FOUND", "Candidate not found"))
|
||||||
|
candidate: Candidate = Candidate.model_validate(candidate_data)
|
||||||
candidate = Candidate.model_validate(candidates_list[0])
|
file_name = f"resume-{resume_id}.png"
|
||||||
|
|
||||||
job = None
|
|
||||||
if job_id:
|
|
||||||
job_data = await database.get_job(job_id)
|
|
||||||
if not job_data:
|
|
||||||
logger.warning(f"⚠️ Job not found for ID: {job_id}")
|
|
||||||
return JSONResponse(
|
|
||||||
status_code=404,
|
|
||||||
content=create_error_response("JOB_NOT_FOUND", f"Job with id '{job_id}' not found"),
|
|
||||||
)
|
|
||||||
job = Job.model_validate(job_data)
|
|
||||||
|
|
||||||
file_name = f"{job.id}.png" if job else "qrcode.png"
|
|
||||||
file_path = pathlib.Path(defines.user_dir) / candidate.username / file_name
|
file_path = pathlib.Path(defines.user_dir) / candidate.username / file_name
|
||||||
if not file_path.exists():
|
if not file_path.exists():
|
||||||
import pyqrcode
|
import pyqrcode
|
||||||
|
|
||||||
if job:
|
qrobj = pyqrcode.create(f"{defines.frontend_url}/chat/{resume.id}")
|
||||||
qrobj = pyqrcode.create(f"{defines.frontend_url}/job-analysis/{candidate.id}/{job.id}/2")
|
|
||||||
else:
|
|
||||||
qrobj = pyqrcode.create(f"{defines.frontend_url}/u/{candidate.id}")
|
|
||||||
with open(file_path, "wb") as f:
|
with open(file_path, "wb") as f:
|
||||||
qrobj.png(f, scale=2)
|
qrobj.png(f, scale=2)
|
||||||
return FileResponse(
|
return FileResponse(
|
||||||
file_path,
|
file_path,
|
||||||
media_type=f"image/{file_path.suffix[1:]}", # Get extension without dot
|
media_type=f"image/{file_path.suffix[1:]}", # Get extension without dot
|
||||||
filename="qrcode.png",
|
filename=file_name,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(backstory_traceback.format_exc())
|
logger.error(backstory_traceback.format_exc())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user