jobs and resume
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
import DefaulModal from '@/components/DefaulModal'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useState, type FC, useEffect } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { ResumeType } from '../types/Types'
|
||||
import { useGetJobs } from '../hooks/useJobsData'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import * as api from '../service/JobsService'
|
||||
import { toast } from '@/components/Toast'
|
||||
import Input from '@/components/Input'
|
||||
import DatePickerComponent from '@/components/DatePicker'
|
||||
import Select from '@/components/Select'
|
||||
import { DocumentUpload, CloseCircle } from 'iconsax-react'
|
||||
import { extractErrorMessage } from '@/helpers/errorUtils'
|
||||
|
||||
interface ResumeProps {
|
||||
jobId?: string
|
||||
}
|
||||
|
||||
const Resume: FC<ResumeProps> = ({ jobId }) => {
|
||||
const [open, setOpen] = useState<boolean>(false)
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
||||
const [filePreview, setFilePreview] = useState<string>('')
|
||||
|
||||
const { data: jobsData } = useGetJobs()
|
||||
|
||||
const { register, handleSubmit, formState: { errors }, setValue, watch, reset } = useForm<ResumeType>()
|
||||
|
||||
const jobs = jobsData?.results?.jobs || []
|
||||
|
||||
const jobOptions = jobs.map(job => ({
|
||||
label: job.title,
|
||||
value: job._id
|
||||
}))
|
||||
|
||||
const watchedJob = watch('job')
|
||||
|
||||
useEffect(() => {
|
||||
if (jobId) {
|
||||
setValue('job', jobId)
|
||||
}
|
||||
}, [jobId, setValue])
|
||||
|
||||
const uploadFileMutation = useMutation({
|
||||
mutationFn: api.uploadSingleFile,
|
||||
onSuccess: (data) => {
|
||||
setValue('resumeUrl', data?.results?.url?.url)
|
||||
toast('فایل با موفقیت آپلود شد', 'success')
|
||||
},
|
||||
onError: () => {
|
||||
toast('خطا در آپلود فایل', 'error')
|
||||
setSelectedFile(null)
|
||||
setFilePreview('')
|
||||
}
|
||||
})
|
||||
|
||||
const sendResumeMutation = useMutation({
|
||||
mutationFn: api.sendResume,
|
||||
onSuccess: () => {
|
||||
toast('رزومه شما با موفقیت ارسال شد', 'success')
|
||||
setOpen(false)
|
||||
reset()
|
||||
setSelectedFile(null)
|
||||
setFilePreview('')
|
||||
},
|
||||
onError: (error) => {
|
||||
toast(extractErrorMessage(error), 'error')
|
||||
}
|
||||
})
|
||||
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0]
|
||||
if (file) {
|
||||
// بررسی نوع فایل
|
||||
if (!file.type.includes('pdf') && !file.type.includes('word') && !file.type.includes('document')) {
|
||||
toast('لطفا فایل PDF یا Word انتخاب کنید', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
// بررسی اندازه فایل (حداکثر 5MB)
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
toast('حجم فایل نباید بیشتر از ۵ مگابایت باشد', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
setSelectedFile(file)
|
||||
setFilePreview(file.name)
|
||||
// آپلود فایل
|
||||
uploadFileMutation.mutate(file)
|
||||
}
|
||||
}
|
||||
|
||||
const removeFile = () => {
|
||||
setSelectedFile(null)
|
||||
setFilePreview('')
|
||||
setValue('resumeUrl', '')
|
||||
}
|
||||
|
||||
const onSubmit = (data: ResumeType) => {
|
||||
if (!selectedFile) {
|
||||
toast('لطفا فایل رزومه را انتخاب کنید', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
if (!data.resumeUrl) {
|
||||
toast('لطفا منتظر تکمیل آپلود فایل باشید', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
sendResumeMutation.mutate(data)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={() => setOpen(!open)} size={'sm'}>
|
||||
ارسال رزومه
|
||||
</Button>
|
||||
|
||||
<DefaulModal
|
||||
open={open}
|
||||
close={() => setOpen(false)}
|
||||
title_header='ارسال رزومه'
|
||||
isHeader
|
||||
>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
||||
{/* انتخاب شغل */}
|
||||
<Select
|
||||
placeholder="انتخاب شغل"
|
||||
items={jobOptions}
|
||||
value={watchedJob}
|
||||
name="job"
|
||||
onChange={(e) => setValue('job', e.target.value)}
|
||||
error_text={errors.job?.message}
|
||||
disabled={!!jobId}
|
||||
/>
|
||||
|
||||
{/* نام کامل */}
|
||||
<Input
|
||||
label="نام کامل"
|
||||
placeholder="نام کامل خود را وارد کنید"
|
||||
{...register('fullName', { required: 'نام کامل الزامی است' })}
|
||||
error_text={errors.fullName?.message}
|
||||
/>
|
||||
|
||||
{/* نام پدر */}
|
||||
<Input
|
||||
label="نام پدر"
|
||||
placeholder="نام پدر خود را وارد کنید"
|
||||
{...register('fatherName', { required: 'نام پدر الزامی است' })}
|
||||
error_text={errors.fatherName?.message}
|
||||
/>
|
||||
|
||||
{/* محل تولد */}
|
||||
<Input
|
||||
label="محل تولد"
|
||||
placeholder="محل تولد خود را وارد کنید"
|
||||
{...register('placeOfBirth', { required: 'محل تولد الزامی است' })}
|
||||
error_text={errors.placeOfBirth?.message}
|
||||
/>
|
||||
|
||||
{/* تاریخ تولد */}
|
||||
<DatePickerComponent
|
||||
label="تاریخ تولد"
|
||||
placeholder="تاریخ تولد را انتخاب کنید"
|
||||
onChange={(date) => setValue('birthday', date)}
|
||||
error_text={errors.birthday?.message}
|
||||
/>
|
||||
|
||||
{/* آپلود فایل رزومه */}
|
||||
<div className="w-full">
|
||||
<label className="text-sm text-black block mb-2">
|
||||
فایل رزومه (PDF یا Word)
|
||||
</label>
|
||||
|
||||
{!selectedFile ? (
|
||||
<div className="relative">
|
||||
<input
|
||||
type="file"
|
||||
accept=".pdf,.doc,.docx"
|
||||
onChange={handleFileChange}
|
||||
className="absolute inset-0 w-full h-full opacity-0 cursor-pointer"
|
||||
id="resume-file"
|
||||
disabled={uploadFileMutation.isPending}
|
||||
/>
|
||||
<label
|
||||
htmlFor="resume-file"
|
||||
className="flex items-center justify-center w-full h-32 border-2 border-dashed border-gray-300 rounded-xl cursor-pointer hover:border-gray-400 transition-colors"
|
||||
>
|
||||
<div className="text-center">
|
||||
<DocumentUpload color='gray' size={32} className="mx-auto text-gray-400 mb-2" />
|
||||
<p className="text-sm text-gray-600">
|
||||
فایل رزومه خود را اینجا رها کنید یا کلیک کنید
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
PDF یا Word (حداکثر ۵ مگابایت)
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-between p-3 bg-gray-50 rounded-xl border">
|
||||
<div className="flex items-center gap-2">
|
||||
<DocumentUpload color='gray' size={20} className="text-gray-600" />
|
||||
<span className="text-sm text-gray-700 truncate">
|
||||
{filePreview}
|
||||
</span>
|
||||
{uploadFileMutation.isPending && (
|
||||
<span className="text-xs text-blue-600">در حال آپلود...</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={removeFile}
|
||||
disabled={uploadFileMutation.isPending}
|
||||
className="text-red-500 hover:text-red-700 disabled:opacity-50"
|
||||
>
|
||||
<CloseCircle color='red' size={20} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{errors.resumeUrl && (
|
||||
<div className="text-xs text-red-500 mt-1">
|
||||
{errors.resumeUrl.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* دکمههای اقدام */}
|
||||
<div className="flex gap-3 pt-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={() => setOpen(false)}
|
||||
className="flex-1"
|
||||
>
|
||||
انصراف
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={sendResumeMutation.isPending || uploadFileMutation.isPending || !watch('resumeUrl')}
|
||||
className="flex-1"
|
||||
>
|
||||
{sendResumeMutation.isPending ? 'در حال ارسال...' :
|
||||
uploadFileMutation.isPending ? 'در حال آپلود فایل...' : 'ارسال رزومه'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DefaulModal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Resume
|
||||
Reference in New Issue
Block a user