change some files
This commit is contained in:
75
ui/forms/course/CourseForm.tsx
Normal file
75
ui/forms/course/CourseForm.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { withFormik, type FormikBag } from "formik";
|
||||
import InnerCourseForm from "./InnerCourseForm";
|
||||
|
||||
export interface CourseItem {
|
||||
id: string | number;
|
||||
title: string;
|
||||
institution: string;
|
||||
year: number | string;
|
||||
duration: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface CourseFormValues {
|
||||
courses: CourseItem[];
|
||||
}
|
||||
|
||||
/** این بخش را با WizardFormData واقعی پروژهات هماهنگ کن */
|
||||
export interface WizardFormData {
|
||||
courses: CourseItem[];
|
||||
// ... other steps
|
||||
}
|
||||
|
||||
export type CourseFormProps = {
|
||||
step: number;
|
||||
setStep: React.Dispatch<React.SetStateAction<number>>;
|
||||
data: WizardFormData;
|
||||
update: (patch: Partial<WizardFormData>) => void;
|
||||
};
|
||||
|
||||
export const COURSE_EMPTY_ITEM: CourseItem = {
|
||||
id: "",
|
||||
title: "",
|
||||
institution: "",
|
||||
year: "",
|
||||
duration: "",
|
||||
description: "",
|
||||
};
|
||||
|
||||
export const COURSE_EMPTY_VALUES: CourseFormValues = {
|
||||
courses: [COURSE_EMPTY_ITEM],
|
||||
};
|
||||
|
||||
const CourseForm = withFormik<CourseFormProps, CourseFormValues>({
|
||||
displayName: "CourseForm",
|
||||
|
||||
enableReinitialize: true,
|
||||
|
||||
mapPropsToValues: (props) => {
|
||||
return {
|
||||
courses:
|
||||
props.data?.courses?.length > 0
|
||||
? props.data.courses
|
||||
: COURSE_EMPTY_VALUES.courses,
|
||||
};
|
||||
},
|
||||
|
||||
// validationSchema: CourseValidationSchema,
|
||||
|
||||
handleSubmit: async (
|
||||
values,
|
||||
bag: FormikBag<CourseFormProps, CourseFormValues>,
|
||||
) => {
|
||||
const { props, setSubmitting } = bag;
|
||||
|
||||
props.update({ courses: values.courses });
|
||||
props.setStep((prev) => prev + 1);
|
||||
|
||||
setSubmitting(false);
|
||||
},
|
||||
})(InnerCourseForm);
|
||||
|
||||
export default CourseForm;
|
||||
207
ui/forms/course/InnerCourseForm.tsx
Normal file
207
ui/forms/course/InnerCourseForm.tsx
Normal file
@@ -0,0 +1,207 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import {
|
||||
Box,
|
||||
TextField,
|
||||
IconButton,
|
||||
Button,
|
||||
Typography,
|
||||
} 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 { COURSE_EMPTY_ITEM, CourseFormProps, CourseFormValues, CourseItem } from "./CourseForm";
|
||||
|
||||
|
||||
type Props = FormikProps<CourseFormValues> & CourseFormProps;
|
||||
|
||||
export default function InnerCourseForm(props: Props) {
|
||||
const { values, errors, touched, handleChange, setFieldValue } = props;
|
||||
|
||||
const handleBack = () => {
|
||||
props.update({ courses: props.values.courses });
|
||||
props.setStep(props.step - 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<FieldArray name="courses">
|
||||
{({ push, remove }) => (
|
||||
<>
|
||||
{values.courses.map((course: CourseItem, index: number) => {
|
||||
const itemErrors = getIn(errors, `courses.${index}`) || {};
|
||||
const itemTouched = getIn(touched, `courses.${index}`) || {};
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={index}
|
||||
sx={{
|
||||
mb: 3,
|
||||
position: "relative",
|
||||
border: "1px solid #e2e8f0",
|
||||
borderRadius: "16px",
|
||||
p: 2,
|
||||
backgroundColor: "#fff",
|
||||
}}
|
||||
>
|
||||
{values.courses.length > 1 && (
|
||||
<IconButton
|
||||
onClick={() => remove(index)}
|
||||
color="error"
|
||||
sx={{ position: "absolute", top: 8, right: 8, zIndex: 2 }}
|
||||
aria-label="remove-course"
|
||||
>
|
||||
<DeleteOutlineOutlinedIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 700,
|
||||
mb: 2,
|
||||
color: "#0f172a",
|
||||
}}
|
||||
>
|
||||
دوره {index + 1}
|
||||
</Typography>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" },
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="عنوان دوره"
|
||||
name={`courses.${index}.title`}
|
||||
value={course.title}
|
||||
onChange={handleChange}
|
||||
error={!!itemTouched.title && !!itemErrors.title}
|
||||
helperText={itemTouched.title ? itemErrors.title : ""}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="موسسه برگزار کننده"
|
||||
name={`courses.${index}.institution`}
|
||||
value={course.institution}
|
||||
onChange={handleChange}
|
||||
error={
|
||||
!!itemTouched.institution && !!itemErrors.institution
|
||||
}
|
||||
helperText={
|
||||
itemTouched.institution ? itemErrors.institution : ""
|
||||
}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
type="number"
|
||||
label="سال برگزاری"
|
||||
name={`courses.${index}.year`}
|
||||
value={course.year}
|
||||
onChange={(e) =>
|
||||
setFieldValue(
|
||||
`courses.${index}.year`,
|
||||
e.target.value === "" ? "" : Number(e.target.value),
|
||||
)
|
||||
}
|
||||
error={!!itemTouched.year && !!itemErrors.year}
|
||||
helperText={itemTouched.year ? itemErrors.year : ""}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
label="مدت دوره"
|
||||
name={`courses.${index}.duration`}
|
||||
value={course.duration}
|
||||
onChange={handleChange}
|
||||
placeholder="مثلاً 40 ساعت"
|
||||
error={!!itemTouched.duration && !!itemErrors.duration}
|
||||
helperText={itemTouched.duration ? itemErrors.duration : ""}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
minRows={2}
|
||||
label="توضیحات"
|
||||
name={`courses.${index}.description`}
|
||||
value={course.description || ""}
|
||||
onChange={handleChange}
|
||||
error={
|
||||
!!itemTouched.description && !!itemErrors.description
|
||||
}
|
||||
helperText={
|
||||
itemTouched.description ? itemErrors.description : ""
|
||||
}
|
||||
sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="outlined"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() =>
|
||||
push({
|
||||
...COURSE_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}
|
||||
type="button"
|
||||
onClick={handleBack}
|
||||
sx={{
|
||||
borderRadius: "12px",
|
||||
color: "#64748b",
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
بازگشت
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
type="submit"
|
||||
sx={{
|
||||
borderRadius: "12px",
|
||||
px: 4,
|
||||
py: 1.5,
|
||||
bgcolor: `${props.step === 12 ? "green" : "#2563eb"}`,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{props.step === 12 ? "اتمام و ثبت نهايي" : "گام بعدی"}
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
</FieldArray>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
14
ui/forms/course/constant/index.ts
Normal file
14
ui/forms/course/constant/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { CourseFormValues, CourseItem } from "../CourseForm";
|
||||
|
||||
export const COURSE_EMPTY_ITEM: CourseItem = {
|
||||
id: "",
|
||||
title: "",
|
||||
institution: "",
|
||||
year: "",
|
||||
duration: "",
|
||||
description: "",
|
||||
};
|
||||
|
||||
export const COURSE_EMPTY_VALUES: CourseFormValues = {
|
||||
courses: [COURSE_EMPTY_ITEM],
|
||||
};
|
||||
18
ui/forms/course/validation/index.ts
Normal file
18
ui/forms/course/validation/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import * as Yup from "yup";
|
||||
|
||||
export const CourseValidationSchema = Yup.object({
|
||||
courses: Yup.array()
|
||||
.of(
|
||||
Yup.object({
|
||||
id: Yup.mixed(),
|
||||
title: Yup.string().required("عنوان دوره الزامی است"),
|
||||
institution: Yup.string().required("موسسه برگزار کننده الزامی است"),
|
||||
year: Yup.number()
|
||||
.typeError("سال برگزاری معتبر نیست")
|
||||
.required("سال برگزاری الزامی است"),
|
||||
duration: Yup.string().required("مدت دوره الزامی است"),
|
||||
description: Yup.string().optional(),
|
||||
}),
|
||||
)
|
||||
.min(1, "حداقل یک دوره باید ثبت شود"),
|
||||
});
|
||||
Reference in New Issue
Block a user