'use client' import { useState, useEffect } from 'react' // @ts-ignore import InputMask from 'react-input-mask' import { ResumeCreate } from '@/types/api' import { useCreateResume, useResumesByVacancy } from '@/hooks/useResume' import { Upload, FileText, X, CheckCircle, Clock, Loader, AlertCircle, Mic } from 'lucide-react' interface ResumeUploadFormProps { vacancyId: number vacancyTitle: string onSuccess?: () => void } export default function ResumeUploadForm({ vacancyId, vacancyTitle, onSuccess }: ResumeUploadFormProps) { const [formData, setFormData] = useState({ applicant_name: '', applicant_email: '', applicant_phone: '', cover_letter: '', }) const [file, setFile] = useState(null) const [success, setSuccess] = useState(false) const [micError, setMicError] = useState(null) const [isCheckingMic, setIsCheckingMic] = useState(false) const [dragActive, setDragActive] = useState(false) const createResumeMutation = useCreateResume() const { data: existingResumes, isLoading: isLoadingResumes, refetch } = useResumesByVacancy(vacancyId) // Проверяем есть ли уже резюме для этой вакансии в текущей сессии const hasExistingResume = existingResumes && existingResumes.length > 0 // Находим непарсенные резюме const pendingResumes = existingResumes?.filter(resume => resume.status === 'pending' || resume.status === 'parsing' ) || [] const hasPendingResumes = pendingResumes.length > 0 // Автообновление для непарсенных резюме useEffect(() => { if (hasPendingResumes) { const interval = setInterval(() => { refetch() }, 3000) // 3 секунды return () => clearInterval(interval) } }, [hasPendingResumes, refetch]) const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target setFormData(prev => ({ ...prev, [name]: value })) } const validateFile = (file: File) => { // Check file size (max 10MB) if (file.size > 10 * 1024 * 1024) { return false } // Check file type const allowedTypes = [ 'application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ] return allowedTypes.includes(file.type) } const handleFileSelect = (selectedFile: File) => { if (validateFile(selectedFile)) { setFile(selectedFile) } } const handleFileChange = (e: React.ChangeEvent) => { const selectedFile = e.target.files?.[0] if (selectedFile) { handleFileSelect(selectedFile) } } const handleDrag = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() if (e.type === 'dragenter' || e.type === 'dragover') { setDragActive(true) } else if (e.type === 'dragleave') { setDragActive(false) } } const handleDrop = (e: React.DragEvent) => { e.preventDefault() e.stopPropagation() setDragActive(false) if (e.dataTransfer.files && e.dataTransfer.files[0]) { handleFileSelect(e.dataTransfer.files[0]) } } const removeFile = () => { setFile(null) } const handleSubmit = (e: React.FormEvent) => { e.preventDefault() console.log('Submit data check:', { file, formData, vacancyId }) if (!file) { console.log('No file selected') return } if (!formData.applicant_name || !formData.applicant_email) { console.log('Missing required fields:', { applicant_name: formData.applicant_name, applicant_email: formData.applicant_email }) return } const resumeData: ResumeCreate = { vacancy_id: vacancyId, applicant_name: formData.applicant_name, applicant_email: formData.applicant_email, applicant_phone: formData.applicant_phone || undefined, cover_letter: formData.cover_letter || undefined, resume_file: file, } console.log('Sending resume data:', resumeData) createResumeMutation.mutate(resumeData, { onSuccess: () => { setSuccess(true) // Reset form setFormData({ applicant_name: '', applicant_email: '', applicant_phone: '', cover_letter: '', }) setFile(null) if (onSuccess) { onSuccess() } } }) } if (isLoadingResumes) { return (
Проверяем ваши заявки...
) } const getStatusDisplay = (status: string) => { switch (status) { case 'pending': return 'Обрабатывается' case 'parsing': return 'Обрабатывается' case 'parse_failed': return 'Ошибка обработки' case 'parsed': return 'Обработано' case 'under_review': return 'На проверке' case 'interview_scheduled': return 'Собеседование назначено' case 'interviewed': return 'Проведено собеседование' case 'accepted': return 'Принят' case 'rejected': return 'Отклонено' default: return status } } // Обработка ошибок парсинга const hasParseFailedResumes = existingResumes?.some(resume => resume.status === 'parse_failed') || false if (hasParseFailedResumes) { return (

Ошибка обработки резюме

Не удалось обработать ваше резюме. Попробуйте загрузить файл в другом формате (PDF, DOCX) или обратитесь к нам за помощью.

{existingResumes?.map((resume) => (
Отправлено: {new Date(resume.created_at).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', hour: '2-digit', minute: '2-digit' })} • Статус: {getStatusDisplay(resume.status)}
))}
) } // Показываем крутилку для статусов pending/parsing if (hasPendingResumes) { return (

Обрабатываем ваше резюме...

Пожалуйста, подождите. Мы анализируем ваше резюме и готовим персональные вопросы для собеседования.

{existingResumes?.map((resume) => (
Отправлено: {new Date(resume.created_at).toLocaleDateString('ru-RU', { day: 'numeric', month: 'long', hour: '2-digit', minute: '2-digit' })}
Статус: {getStatusDisplay(resume.status)}
))}
) } // Обычное успешное состояние для parsed и других завершенных статусов if (success || hasExistingResume) { return (
{hasExistingResume && existingResumes && existingResumes.map((resume) => (
{/* Status and Date Row */}

Резюме

{new Date(resume.created_at).toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' })}

{getStatusDisplay(resume.status)}
{/* Content based on status */} {resume.status === 'parsed' && (

Мы готовы!

Ваше резюме успешно обработано. Можете приступать к собеседованию с HR-агентом.

* Вы можете пройти собеседование сегодня до 20:00 МСК

{micError && (

{micError}

)}
)} {(resume.status === 'parsing' || resume.status === 'pending') && (

Обрабатываем ваше резюме

Анализируем опыт и готовим персональные вопросы

)} {resume.status === 'parse_failed' && (
⚠️

Ошибка обработки

Попробуйте загрузить файл в другом формате

)} {resume.status === 'rejected' && (

Резюме не соответствует вакансии

К сожалению, ваш опыт не подходит для данной позиции

)} {!['parsed', 'parsing', 'pending', 'parse_failed', 'rejected'].includes(resume.status) && (

{getStatusDisplay(resume.status)}

Мы свяжемся с вами для следующих шагов

)}
))}
) } return (

Откликнуться

{vacancyTitle}

{/* Personal Information */}
{() => ( )}
{/* Cover Letter */}