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,190 @@
"use client";
import React from "react";
import {
Box,
Paper,
TextField,
Typography,
Alert,
AlertTitle,
Divider,
Button,
} from "@mui/material";
import { FieldArray, Form, getIn, type FormikProps } from "formik";
import type { RelationFormProps, RelationFormValues } from "./types";
type Props = FormikProps<RelationFormValues> & RelationFormProps;
export default function InnerRelationForm(props: Props) {
const {
values,
errors,
touched,
handleChange,
setFieldValue,
isSubmitting,
} = props;
const handleBack = () => {
props.update({ relations: values.relations });
props.setStep(props.step - 1);
};
return (
<Form>
<Paper
elevation={0}
sx={{
p: { xs: 2, md: 3 },
borderRadius: "24px",
border: "1px solid #e2e8f0",
backgroundColor: "#fff",
}}
>
<Alert severity="warning" sx={{ mb: 3, borderRadius: "12px" }}>
<AlertTitle sx={{ fontWeight: 800 }}>توجه</AlertTitle>
مشخصات دو نفر از آشنایان را وارد کنید و از درج بستگان درجه یک (پدر،
مادر، همسر، برادر و خواهر) خودداری نمایید.
</Alert>
<FieldArray name="relations">
{() => (
<Box>
{values.relations.map((item, index) => {
const itemErrors = getIn(errors, `relations.${index}`) || {};
const itemTouched = getIn(touched, `relations.${index}`) || {};
return (
<Box key={item.id || index} sx={{ mb: index === 0 ? 4 : 0 }}>
<Typography
variant="subtitle1"
sx={{ fontWeight: 800, mb: 2, color: "#1e293b" }}
>
آشنای {index === 0 ? "اول" : "دوم"}
</Typography>
<Box
sx={{
display: "grid",
gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" },
gap: 2.5,
}}
>
<TextField
label="نام*"
name={`relations.${index}.firstName`}
value={item.firstName}
onChange={handleChange}
fullWidth
error={!!itemTouched.firstName && !!itemErrors.firstName}
helperText={itemTouched.firstName ? itemErrors.firstName : ""}
/>
<TextField
label="نام خانوادگی*"
name={`relations.${index}.lastName`}
value={item.lastName}
onChange={handleChange}
fullWidth
error={!!itemTouched.lastName && !!itemErrors.lastName}
helperText={itemTouched.lastName ? itemErrors.lastName : ""}
/>
<TextField
label="نسبت*"
name={`relations.${index}.relationship`}
value={item.relationship}
onChange={handleChange}
fullWidth
placeholder="مثلاً: همکار، دوست"
error={!!itemTouched.relationship && !!itemErrors.relationship}
helperText={
itemTouched.relationship ? itemErrors.relationship : ""
}
/>
<TextField
label="تلفن تماس*"
name={`relations.${index}.phoneNumber`}
value={item.phoneNumber}
onChange={(e) => {
const val = e.target.value.replace(/[^\d]/g, "");
setFieldValue(`relations.${index}.phoneNumber`, val);
}}
fullWidth
inputMode="tel"
error={!!itemTouched.phoneNumber && !!itemErrors.phoneNumber}
helperText={
itemTouched.phoneNumber ? itemErrors.phoneNumber : ""
}
/>
<TextField
label="شغل*"
name={`relations.${index}.jobTitle`}
value={item.jobTitle}
onChange={handleChange}
fullWidth
error={!!itemTouched.jobTitle && !!itemErrors.jobErrors}
helperText={itemTouched.jobTitle ? itemErrors.jobTitle : ""}
/>
<TextField
label="نام محل کار*"
name={`relations.${index}.workplaceName`}
value={item.workplaceName}
onChange={handleChange}
fullWidth
error={
!!itemTouched.workplaceName && !!itemErrors.workplaceName
}
helperText={
itemTouched.workplaceName ? itemErrors.workplaceName : ""
}
/>
</Box>
{index === 0 && <Divider sx={{ mt: 4 }} />}
</Box>
);
})}
</Box>
)}
</FieldArray>
{/* دکمه‌های ناوبری */}
<Box
sx={{
display: "flex",
justifyContent: "space-between",
mt: 4,
}}
>
<Button
disabled={props.step === 1 || isSubmitting}
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>
</Paper>
</Form>
);
}

View File

@@ -0,0 +1,36 @@
"use client";
import { withFormik, type FormikBag } from "formik";
import type { RelationFormProps, RelationFormValues } from "./types";
import { RELATION_INITIAL_VALUES } from "./constant";
import { RelationValidationSchema } from "./validation";
import InnerRelationForm from "./InnerRelationForm";
const RelationForm = withFormik<RelationFormProps, RelationFormValues>({
displayName: "RelationForm",
enableReinitialize: true,
mapPropsToValues: (props) => {
// اگر داده‌ای از قبل بود استفاده کن، در غیر این صورت مقدار اولیه (۲تایی)
if (props.data?.relations?.length === 2) {
return { relations: props.data.relations };
}
return RELATION_INITIAL_VALUES;
},
validationSchema: RelationValidationSchema,
handleSubmit: async (
values,
bag: FormikBag<RelationFormProps, RelationFormValues>
) => {
const { props, setSubmitting } = bag;
props.update({ relations: values.relations });
props.setStep((prev) => prev + 1);
setSubmitting(false);
},
})(InnerRelationForm);
export default RelationForm;

View File

@@ -0,0 +1,18 @@
import type { ReferenceItem, RelationFormValues } from "../types";
export const EMPTY_REFERENCE_ITEM: ReferenceItem = {
id: "",
firstName: "",
lastName: "",
relationship: "",
jobTitle: "",
workplaceName: "",
phoneNumber: "",
};
export const RELATION_INITIAL_VALUES: RelationFormValues = {
relations: [
{ ...EMPTY_REFERENCE_ITEM, id: 1 },
{ ...EMPTY_REFERENCE_ITEM, id: 2 },
],
};

View File

@@ -0,0 +1,28 @@
import type React from "react";
export interface ReferenceItem {
id?: string | number;
firstName: string;
lastName: string;
relationship: string;
jobTitle: string;
workplaceName: string;
phoneNumber: string;
}
export interface RelationFormValues {
relations: ReferenceItem[]; // طول این آرایه همیشه باید 2 باشد
}
/** هماهنگ با استیت کلی ویزارد شما */
export interface WizardFormData {
relations: ReferenceItem[];
// ... سایر مراحل
}
export interface RelationFormProps {
step: number;
setStep: React.Dispatch<React.SetStateAction<number>>;
data: WizardFormData;
update: (patch: Partial<WizardFormData>) => void;
}

View File

@@ -0,0 +1,27 @@
import * as Yup from "yup";
export const RelationValidationSchema = Yup.object().shape({
relations: Yup.array()
.of(
Yup.object().shape({
firstName: Yup.string().required("نام الزامی است"),
lastName: Yup.string().required("نام خانوادگی الزامی است"),
relationship: Yup.string()
.required("نسبت الزامی است")
.test(
"no-immediate-family",
"درج بستگان درجه یک مجاز نیست",
(value) => {
const forbidden = ["پدر", "مادر", "همسر", "برادر", "خواهر"];
return !forbidden.some((f) => value?.includes(f));
}
),
jobTitle: Yup.string().required("شغل الزامی است"),
workplaceName: Yup.string().required("محل کار الزامی است"),
phoneNumber: Yup.string()
.required("تلفن الزامی است")
.matches(/^0\d{10}$/, "شماره تماس معتبر نیست (۱۱ رقم با ۰)"),
})
)
.length(2, "باید مشخصات دو نفر را وارد کنید"),
});