change some files
This commit is contained in:
334
ui/forms/jobRequest/InnerJobRequestForm.tsx
Normal file
334
ui/forms/jobRequest/InnerJobRequestForm.tsx
Normal file
@@ -0,0 +1,334 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
Box,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Typography,
|
||||
Button,
|
||||
IconButton,
|
||||
Paper,
|
||||
} from "@mui/material";
|
||||
import DeleteOutlineOutlinedIcon from "@mui/icons-material/DeleteOutlineOutlined";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import { FieldArray, Form, getIn, type FormikProps } from "formik";
|
||||
|
||||
import type {
|
||||
JobRequestFormProps,
|
||||
JobRequestFormValues,
|
||||
JobRequestItem,
|
||||
} from "./types";
|
||||
import {
|
||||
JOB_REQUEST_EMPTY_ITEM,
|
||||
defaultCategories,
|
||||
defaultJobs,
|
||||
relationTypes,
|
||||
shiftTypes,
|
||||
} from "./constant";
|
||||
|
||||
type Props = FormikProps<JobRequestFormValues> & JobRequestFormProps;
|
||||
|
||||
export default function InnerJobRequestForm(props: Props) {
|
||||
const {
|
||||
values,
|
||||
errors,
|
||||
touched,
|
||||
handleChange,
|
||||
setFieldValue,
|
||||
jobCategories = defaultCategories,
|
||||
jobs = defaultJobs,
|
||||
isSubmitting,
|
||||
} = props;
|
||||
|
||||
const handleBack = () => {
|
||||
props.update({
|
||||
jobRequests: props.values.jobRequests,
|
||||
});
|
||||
props.setStep(props.step - 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<FieldArray name="jobRequests">
|
||||
{({ push, remove }) => (
|
||||
<>
|
||||
{values.jobRequests.map((item: JobRequestItem, index: number) => {
|
||||
const itemErrors = getIn(errors, `jobRequests.${index}`) || {};
|
||||
const itemTouched = getIn(touched, `jobRequests.${index}`) || {};
|
||||
|
||||
const filteredJobs = jobs.filter(
|
||||
(job) => job.jobCategoryId === item.jobCategoryId,
|
||||
);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
key={item.id || index}
|
||||
elevation={0}
|
||||
sx={{
|
||||
borderRadius: "24px",
|
||||
backgroundColor: "#ffffff",
|
||||
border: "1px solid #e2e8f0",
|
||||
p: 3,
|
||||
mb: 3,
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
{values.jobRequests.length > 1 && (
|
||||
<IconButton
|
||||
onClick={() => remove(index)}
|
||||
color="error"
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 12,
|
||||
right: 12,
|
||||
zIndex: 1,
|
||||
}}
|
||||
aria-label="remove-job-request"
|
||||
>
|
||||
<DeleteOutlineOutlinedIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 700,
|
||||
mb: 3,
|
||||
color: "#0f172a",
|
||||
}}
|
||||
>
|
||||
درخواست شغلی {index + 1}
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns:
|
||||
"repeat(auto-fit, minmax(340px, 1fr))",
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
label="رسته شغلی*"
|
||||
name={`jobRequests.${index}.jobCategoryId`}
|
||||
value={item.jobCategoryId}
|
||||
onChange={(e) => {
|
||||
const categoryId = e.target.value;
|
||||
setFieldValue(
|
||||
`jobRequests.${index}.jobCategoryId`,
|
||||
categoryId,
|
||||
);
|
||||
setFieldValue(`jobRequests.${index}.jobId`, "");
|
||||
}}
|
||||
error={
|
||||
!!itemTouched.jobCategoryId &&
|
||||
!!itemErrors.jobCategoryId
|
||||
}
|
||||
helperText={
|
||||
itemTouched.jobCategoryId
|
||||
? itemErrors.jobCategoryId
|
||||
: " "
|
||||
}
|
||||
>
|
||||
<MenuItem value="">انتخاب...</MenuItem>
|
||||
{jobCategories.map((category) => (
|
||||
<MenuItem key={category.id} value={category.id}>
|
||||
{category.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
label="شغل درخواستی*"
|
||||
name={`jobRequests.${index}.jobId`}
|
||||
value={item.jobId}
|
||||
onChange={handleChange}
|
||||
error={!!itemTouched.jobId && !!itemErrors.jobId}
|
||||
helperText={itemTouched.jobId ? itemErrors.jobId : " "}
|
||||
disabled={!item.jobCategoryId}
|
||||
>
|
||||
<MenuItem value="">انتخاب...</MenuItem>
|
||||
{filteredJobs.map((job) => (
|
||||
<MenuItem key={job.id} value={job.id}>
|
||||
{job.title}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="توضیحات شغل درخواست"
|
||||
name={`jobRequests.${index}.requestedJobDescription`}
|
||||
value={item.requestedJobDescription}
|
||||
onChange={handleChange}
|
||||
error={
|
||||
!!itemTouched.requestedJobDescription &&
|
||||
!!itemErrors.requestedJobDescription
|
||||
}
|
||||
helperText={
|
||||
itemTouched.requestedJobDescription
|
||||
? itemErrors.requestedJobDescription
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
label="نوع رابطه کاری*"
|
||||
name={`jobRequests.${index}.employmentRelationType`}
|
||||
value={item.employmentRelationType}
|
||||
onChange={handleChange}
|
||||
error={
|
||||
!!itemTouched.employmentRelationType &&
|
||||
!!itemErrors.employmentRelationType
|
||||
}
|
||||
helperText={
|
||||
itemTouched.employmentRelationType
|
||||
? itemErrors.employmentRelationType
|
||||
: " "
|
||||
}
|
||||
>
|
||||
<MenuItem value="">انتخاب...</MenuItem>
|
||||
{relationTypes.map((relation) => (
|
||||
<MenuItem key={relation} value={relation}>
|
||||
{relation}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
label="نوع شیفت درخواستی"
|
||||
name={`jobRequests.${index}.requestedShiftType`}
|
||||
value={item.requestedShiftType}
|
||||
onChange={handleChange}
|
||||
error={
|
||||
!!itemTouched.requestedShiftType &&
|
||||
!!itemErrors.requestedShiftType
|
||||
}
|
||||
helperText={
|
||||
itemTouched.requestedShiftType
|
||||
? itemErrors.requestedShiftType
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<MenuItem value="">انتخاب...</MenuItem>
|
||||
{shiftTypes.map((shift) => (
|
||||
<MenuItem key={shift} value={shift}>
|
||||
{shift}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="حقوق درخواستی (ریال)"
|
||||
name={`jobRequests.${index}.expectedSalary`}
|
||||
value={item.expectedSalary}
|
||||
onChange={(e) => {
|
||||
const onlyDigits = e.target.value.replace(/[^\d]/g, "");
|
||||
setFieldValue(
|
||||
`jobRequests.${index}.expectedSalary`,
|
||||
onlyDigits,
|
||||
);
|
||||
}}
|
||||
error={
|
||||
!!itemTouched.expectedSalary &&
|
||||
!!itemErrors.expectedSalary
|
||||
}
|
||||
helperText={
|
||||
itemTouched.expectedSalary
|
||||
? itemErrors.expectedSalary
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
|
||||
<Box sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="توضیحات"
|
||||
name={`jobRequests.${index}.description`}
|
||||
value={item.description}
|
||||
onChange={handleChange}
|
||||
multiline
|
||||
minRows={3}
|
||||
error={
|
||||
!!itemTouched.description && !!itemErrors.description
|
||||
}
|
||||
helperText={
|
||||
itemTouched.description ? itemErrors.description : ""
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
})}
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() =>
|
||||
push({
|
||||
...JOB_REQUEST_EMPTY_ITEM,
|
||||
id: Date.now(),
|
||||
})
|
||||
}
|
||||
sx={{
|
||||
borderRadius: "12px",
|
||||
mb: 3,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
افزودن درخواست شغلی
|
||||
</Button>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
mt: 5,
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
disabled={props.step === 1 || isSubmitting}
|
||||
type="button"
|
||||
onClick={handleBack}
|
||||
sx={{
|
||||
borderRadius: "12px",
|
||||
color: "#64748b",
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
بازگشت
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
type="submit"
|
||||
disabled={isSubmitting}
|
||||
sx={{
|
||||
borderRadius: "12px",
|
||||
px: 4,
|
||||
py: 1.5,
|
||||
bgcolor: props.step === 12 ? "green" : "#2563eb",
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{props.step === 12 ? "اتمام و ثبت نهایی" : "گام بعدی"}
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</FieldArray>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
42
ui/forms/jobRequest/JobRequestForm.tsx
Normal file
42
ui/forms/jobRequest/JobRequestForm.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { withFormik, type FormikBag } from "formik";
|
||||
import InnerJobRequestForm from "./InnerJobRequestForm";
|
||||
import type { JobRequestFormProps, JobRequestFormValues } from "./types";
|
||||
import { JobRequestValidationSchema } from "./validation";
|
||||
import { JOB_REQUEST_EMPTY_VALUES } from "./constant";
|
||||
|
||||
const JobRequestForm = withFormik<JobRequestFormProps, JobRequestFormValues>({
|
||||
displayName: "JobRequestForm",
|
||||
|
||||
enableReinitialize: true,
|
||||
|
||||
mapPropsToValues: (props) => {
|
||||
return {
|
||||
jobRequests:
|
||||
props.data?.jobRequests?.length > 0
|
||||
? props.data.jobRequests
|
||||
: JOB_REQUEST_EMPTY_VALUES.jobRequests,
|
||||
};
|
||||
},
|
||||
|
||||
validationSchema: JobRequestValidationSchema,
|
||||
|
||||
handleSubmit: async (
|
||||
values,
|
||||
bag: FormikBag<JobRequestFormProps, JobRequestFormValues>,
|
||||
) => {
|
||||
const { props, setSubmitting } = bag;
|
||||
|
||||
props.update({
|
||||
jobRequests: values.jobRequests,
|
||||
});
|
||||
|
||||
props.setStep((prev) => prev + 1);
|
||||
|
||||
setSubmitting(false);
|
||||
},
|
||||
})(InnerJobRequestForm);
|
||||
|
||||
export default JobRequestForm;
|
||||
50
ui/forms/jobRequest/constant/index.ts
Normal file
50
ui/forms/jobRequest/constant/index.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { JobCategoryOption, JobOption, JobRequestFormValues, JobRequestItem } from "../JobRequestForm";
|
||||
|
||||
|
||||
export const relationTypes = [
|
||||
"تمام وقت",
|
||||
"پاره وقت",
|
||||
"پروژهای",
|
||||
"ساعتی",
|
||||
"قراردادی",
|
||||
"کارورزی",
|
||||
] as const;
|
||||
|
||||
export const shiftTypes = [
|
||||
"ثابت صبح",
|
||||
"ثابت عصر",
|
||||
"ثابت شب",
|
||||
"چرخشی",
|
||||
"شیفتی",
|
||||
"فرقی ندارد",
|
||||
] as const;
|
||||
|
||||
export const defaultCategories: JobCategoryOption[] = [
|
||||
{ id: "1", name: "پاراکلینیک" },
|
||||
{ id: "2", name: "اداری" },
|
||||
{ id: "3", name: "درمانی" },
|
||||
];
|
||||
|
||||
export const defaultJobs: JobOption[] = [
|
||||
{ id: "1", title: "کارشناس آزمایشگاه", jobCategoryId: "1" },
|
||||
{ id: "2", title: "کارشناس رادیولوژی", jobCategoryId: "1" },
|
||||
{ id: "3", title: "منشی", jobCategoryId: "2" },
|
||||
{ id: "4", title: "مسئول بایگانی", jobCategoryId: "2" },
|
||||
{ id: "5", title: "پرستار", jobCategoryId: "3" },
|
||||
{ id: "6", title: "کمک پرستار", jobCategoryId: "3" },
|
||||
];
|
||||
|
||||
export const JOB_REQUEST_EMPTY_ITEM: JobRequestItem = {
|
||||
id: "",
|
||||
jobCategoryId: "",
|
||||
jobId: "",
|
||||
requestedJobDescription: "",
|
||||
employmentRelationType: "",
|
||||
description: "",
|
||||
requestedShiftType: "",
|
||||
expectedSalary: "",
|
||||
};
|
||||
|
||||
export const JOB_REQUEST_EMPTY_VALUES: JobRequestFormValues = {
|
||||
jobRequests: [JOB_REQUEST_EMPTY_ITEM],
|
||||
};
|
||||
44
ui/forms/jobRequest/types/index.ts
Normal file
44
ui/forms/jobRequest/types/index.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type React from "react";
|
||||
|
||||
export type JobCategoryOption = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type JobOption = {
|
||||
id: string;
|
||||
title: string;
|
||||
jobCategoryId: string;
|
||||
};
|
||||
|
||||
export interface JobRequestItem {
|
||||
id: string | number;
|
||||
jobCategoryId: string;
|
||||
jobId: string;
|
||||
requestedJobDescription: string;
|
||||
employmentRelationType: string;
|
||||
description: string;
|
||||
requestedShiftType: string;
|
||||
expectedSalary: string;
|
||||
}
|
||||
|
||||
export interface JobRequestFormValues {
|
||||
jobRequests: JobRequestItem[];
|
||||
}
|
||||
|
||||
/**
|
||||
* این type را با مدل اصلی wizard پروژهات هماهنگ کن
|
||||
*/
|
||||
export interface WizardFormData {
|
||||
jobRequests: JobRequestItem[];
|
||||
// ... سایر step ها
|
||||
}
|
||||
|
||||
export interface JobRequestFormProps {
|
||||
step: number;
|
||||
setStep: React.Dispatch<React.SetStateAction<number>>;
|
||||
data: WizardFormData;
|
||||
update: (patch: Partial<WizardFormData>) => void;
|
||||
jobCategories?: JobCategoryOption[];
|
||||
jobs?: JobOption[];
|
||||
}
|
||||
35
ui/forms/jobRequest/validation/index.ts
Normal file
35
ui/forms/jobRequest/validation/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import * as Yup from "yup";
|
||||
|
||||
export const JobRequestValidationSchema = Yup.object({
|
||||
jobRequests: Yup.array()
|
||||
.of(
|
||||
Yup.object({
|
||||
id: Yup.mixed().optional(),
|
||||
|
||||
jobCategoryId: Yup.string().required("رسته شغلی الزامی است"),
|
||||
|
||||
jobId: Yup.string().required("شغل درخواستی الزامی است"),
|
||||
|
||||
requestedJobDescription: Yup.string()
|
||||
.max(1000, "توضیحات شغل درخواستی نباید بیشتر از 1000 کاراکتر باشد")
|
||||
.optional(),
|
||||
|
||||
employmentRelationType: Yup.string().required(
|
||||
"نوع رابطه کاری الزامی است",
|
||||
),
|
||||
|
||||
description: Yup.string()
|
||||
.max(2000, "توضیحات نباید بیشتر از 2000 کاراکتر باشد")
|
||||
.optional(),
|
||||
|
||||
requestedShiftType: Yup.string().optional(),
|
||||
|
||||
expectedSalary: Yup.string()
|
||||
.matches(/^\d*$/, "حقوق درخواستی فقط باید شامل عدد باشد")
|
||||
.max(15, "حقوق درخواستی بیش از حد طولانی است")
|
||||
.optional(),
|
||||
}),
|
||||
)
|
||||
.min(1, "حداقل یک درخواست شغلی باید ثبت شود")
|
||||
.required("ثبت درخواست شغلی الزامی است"),
|
||||
});
|
||||
Reference in New Issue
Block a user