From afc90b3156f621b2d9b1550409ff6ebef934fd93 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sat, 4 Oct 2025 10:46:58 +0330 Subject: [PATCH] crd job + resume list --- src/config/Pages.ts | 4 + src/pages/jobs/List.tsx | 112 ++++++++++++++++ src/pages/jobs/Resume.tsx | 132 +++++++++++++++++++ src/pages/jobs/components/ModalCreateJob.tsx | 127 ++++++++++++++++++ src/pages/jobs/hooks/useJobsData.ts | 36 +++++ src/pages/jobs/service/JobsService.ts | 22 ++++ src/pages/jobs/types/Types.ts | 60 +++++++++ src/router/Main.tsx | 5 + 8 files changed, 498 insertions(+) create mode 100644 src/pages/jobs/List.tsx create mode 100644 src/pages/jobs/Resume.tsx create mode 100644 src/pages/jobs/components/ModalCreateJob.tsx create mode 100644 src/pages/jobs/hooks/useJobsData.ts create mode 100644 src/pages/jobs/service/JobsService.ts create mode 100644 src/pages/jobs/types/Types.ts diff --git a/src/config/Pages.ts b/src/config/Pages.ts index 2dd09a3..cfab75f 100644 --- a/src/config/Pages.ts +++ b/src/config/Pages.ts @@ -187,4 +187,8 @@ export const Pages = { fines: "/financial/fines", createFine: "/financial/create-fine", }, + jobs: { + list: "/jobs/list", + resumes: "/jobs/resumes", + }, }; diff --git a/src/pages/jobs/List.tsx b/src/pages/jobs/List.tsx new file mode 100644 index 0000000..0fba24e --- /dev/null +++ b/src/pages/jobs/List.tsx @@ -0,0 +1,112 @@ +import { type FC, useState } from 'react' +import { useGetJobs, useDeleteJob } from './hooks/useJobsData' +import { type Job } from './types/Types' +import PageLoading from '../../components/PageLoading' +import Error from '../../components/Error' +import PaginationUi from '../../components/PaginationUi' +import Td from '../../components/Td' +import TrashWithConfrim from '../../components/TrashWithConfrim' +import ModalCreateJob from './components/ModalCreateJob' + +const JobsList: FC = () => { + const [currentPage, setCurrentPage] = useState(1); + const { data: jobsData, isLoading, error, refetch } = useGetJobs(currentPage) + const deleteJobMutation = useDeleteJob(); + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ +
+ ); + } + + const handleDeleteJob = async (jobId: string) => { + await deleteJobMutation.mutateAsync(jobId); + refetch() + }; + + const jobs = jobsData?.results?.jobs || []; + const pager = jobsData?.results?.pager; + + const getJobTypeText = (type: string) => { + switch (type) { + case 'fullTime': return 'تمام وقت'; + case 'partTime': return 'پاره وقت'; + case 'contract': return 'قراردادی'; + default: return type; + } + }; + + const truncateText = (text: string, maxLength: number = 100) => { + return text.length > maxLength ? text.substring(0, maxLength) + '...' : text; + }; + + return ( +
+ +
+ + + + + + + {jobs.length === 0 ? ( + + + + ) : ( + jobs.map((job: Job) => ( + + + + + )) + )} + +
+ + + + + +
+ هیچ شغلی یافت نشد +
+ + + + + {getJobTypeText(job.type)} + + + +
+ handleDeleteJob(job._id)} + isLoading={deleteJobMutation.isPending} + /> +
+
+
+ + { + setCurrentPage(page); + }} + /> +
+ ) +} + +export default JobsList \ No newline at end of file diff --git a/src/pages/jobs/Resume.tsx b/src/pages/jobs/Resume.tsx new file mode 100644 index 0000000..38a019e --- /dev/null +++ b/src/pages/jobs/Resume.tsx @@ -0,0 +1,132 @@ +import { type FC, useState } from 'react' +import { useGetResumes } from './hooks/useJobsData' +import { type Resume } from './types/Types' +import PageLoading from '@/components/PageLoading' +import Error from '@/components/Error' +import PaginationUi from '@/components/PaginationUi' +import Td from '@/components/Td' +import StatusWithText from '@/components/StatusWithText' +import { ArrowLeft2 } from 'iconsax-react' + +const Resumes: FC = () => { + const [currentPage, setCurrentPage] = useState(1); + const { data, isLoading, error } = useGetResumes(currentPage); + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ +
+ ); + } + + const resumes = data?.results?.resumes || []; + const pager = data?.results?.pager; + + const getStatusVariant = (status: string) => { + switch (status) { + case 'Accepted': return 'success'; + case 'Rejected': return 'error'; + case 'Pending': return 'warning'; + default: return 'warning'; + } + }; + + const getStatusText = (status: string) => { + switch (status) { + case 'Accepted': return 'تایید شده'; + case 'Rejected': return 'رد شده'; + case 'Pending': return 'در انتظار بررسی'; + default: return status; + } + }; + + const truncateText = (text: string, maxLength: number = 50) => { + return text.length > maxLength ? text.substring(0, maxLength) + '...' : text; + }; + + const handleDownloadResume = (resumeUrl: string, fullName: string) => { + const link = document.createElement('a'); + link.href = resumeUrl; + link.download = `${fullName}_resume.pdf`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }; + + return ( +
+
+ + + + + + + {resumes.length === 0 ? ( + + + + ) : ( + resumes.map((resume: Resume) => ( + + + + + )) + )} + +
+ + + + + + + +
+ هیچ رزومه‌ای یافت نشد +
+ + + + + + + + +
handleDownloadResume(resume.resumeUrl, resume.fullName)} + className="flex items-center gap-2 cursor-pointer"> + رزرومه + +
+
+
+ + { + setCurrentPage(page); + }} + /> +
+ ) +} + +export default Resumes \ No newline at end of file diff --git a/src/pages/jobs/components/ModalCreateJob.tsx b/src/pages/jobs/components/ModalCreateJob.tsx new file mode 100644 index 0000000..465180d --- /dev/null +++ b/src/pages/jobs/components/ModalCreateJob.tsx @@ -0,0 +1,127 @@ +import Button from '@/components/Button' +import DefaulModal from '@/components/DefaulModal' +import Input from '@/components/Input' +import Select, { type ItemsSelectType } from '@/components/Select' +import Textarea from '@/components/Textarea' +import { useFormik } from 'formik' +import { useState, type FC } from 'react' +import * as Yup from 'yup' +import type { CreateJobType } from '../types/Types' +import { useCreateJob } from '../hooks/useJobsData' + +const ModalCreateJob: FC = () => { + + const createJobMutation = useCreateJob() + const [isOpen, setIsOpen] = useState(false) + + const jobTypeOptions: ItemsSelectType[] = [ + { value: 'fullTime', label: 'تمام وقت' }, + { value: 'partTime', label: 'پاره وقت' }, + { value: 'contract', label: 'قراردادی' }, + ] + + const formik = useFormik({ + initialValues: { + title: '', + description: '', + location: '', + type: 'fullTime', + }, + onSubmit: async (values) => { + try { + await createJobMutation.mutateAsync(values) + setIsOpen(false) + formik.resetForm() + } catch (error) { + console.error('خطا در ایجاد شغل:', error) + } + }, + validationSchema: Yup.object({ + title: Yup.string().required('عنوان شغل الزامی است'), + description: Yup.string().required('توضیحات شغل الزامی است'), + location: Yup.string().required('مکان شغل الزامی است'), + type: Yup.string().required('نوع شغل الزامی است'), + }) + }) + + const handleCloseModal = () => { + setIsOpen(false) + formik.resetForm() + } + + return ( +
+
+
+ + +
+ + +