change some files

This commit is contained in:
2026-06-02 17:08:52 +03:30
parent b8dc1d0e1b
commit cfb48c5bb0
76 changed files with 5204 additions and 2555 deletions

View 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;

View 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>
);
}

View 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],
};

View 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, "حداقل یک دوره باید ثبت شود"),
});