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,320 @@
"use client";
import React from "react";
import {
Box,
Paper,
TextField,
Typography,
IconButton,
Button,
MenuItem,
Divider,
} from "@mui/material";
import AddIcon from "@mui/icons-material/Add";
import { DeleteOutlineOutlined } from "@mui/icons-material";
import { FieldArray, Form, getIn, type FormikProps } from "formik";
import type {
ReferralFormProps,
ReferralFormValues,
ReferralItem,
} from "./types";
import {
REFERRAL_EMPTY_ITEM,
REFERRAL_MIN_ITEMS,
} from "./constant";
type Props = FormikProps<ReferralFormValues> & ReferralFormProps;
function ReferralItemForm({
index,
item,
errors,
touched,
handleChange,
setFieldValue,
onRemove,
disableRemove,
}: {
index: number;
item: ReferralItem;
errors: any;
touched: any;
handleChange: React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement>;
setFieldValue: (field: string, value: any, shouldValidate?: boolean) => void;
onRemove: () => void;
disableRemove: boolean;
}) {
const itemErrors = getIn(errors, `referrals.${index}`) || {};
const itemTouched = getIn(touched, `referrals.${index}`) || {};
return (
<Paper
elevation={0}
sx={{
p: { xs: 2, md: 2.5 },
borderRadius: "20px",
border: "1px solid #e5e7eb",
backgroundColor: "#fff",
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
mb: 1.5,
gap: 2,
}}
>
<Typography sx={{ fontWeight: 700 }}>معرف {index + 1}</Typography>
<IconButton
onClick={onRemove}
disabled={disableRemove}
color="error"
size="small"
aria-label="حذف معرف"
>
<DeleteOutlineOutlined />
</IconButton>
</Box>
<Box
sx={{
display: "grid",
gridTemplateColumns: { xs: "1fr", md: "1fr 1fr" },
gap: 2,
}}
>
<TextField
label="نام"
name={`referrals.${index}.firstName`}
value={item.firstName}
onChange={handleChange}
fullWidth
error={!!itemTouched.firstName && !!itemErrors.firstName}
helperText={itemTouched.firstName ? itemErrors.firstName : ""}
/>
<TextField
label="نام خانوادگی"
name={`referrals.${index}.lastName`}
value={item.lastName}
onChange={handleChange}
fullWidth
error={!!itemTouched.lastName && !!itemErrors.lastName}
helperText={itemTouched.lastName ? itemErrors.lastName : ""}
/>
<TextField
label="نسبت / رابطه"
name={`referrals.${index}.relationship`}
value={item.relationship}
onChange={handleChange}
fullWidth
placeholder="مثلاً: دوست، همکار، فامیل..."
error={!!itemTouched.relationship && !!itemErrors.relationship}
helperText={itemTouched.relationship ? itemErrors.relationship : ""}
/>
<TextField
label="مدت زمان آشنایی"
name={`referrals.${index}.acquaintanceDuration`}
value={item.acquaintanceDuration}
onChange={handleChange}
fullWidth
placeholder="مثلاً: ۵ سال"
error={
!!itemTouched.acquaintanceDuration &&
!!itemErrors.acquaintanceDuration
}
helperText={
itemTouched.acquaintanceDuration
? itemErrors.acquaintanceDuration
: ""
}
/>
<TextField
select
label="نوع آشنایی"
name={`referrals.${index}.acquaintanceType`}
value={item.acquaintanceType}
onChange={handleChange}
fullWidth
error={
!!itemTouched.acquaintanceType &&
!!itemErrors.acquaintanceType
}
helperText={
itemTouched.acquaintanceType ? itemErrors.acquaintanceType : ""
}
>
<MenuItem value="Direct">مستقیم</MenuItem>
<MenuItem value="Indirect">غیرمستقیم</MenuItem>
</TextField>
<TextField
label="تلفن تماس"
name={`referrals.${index}.phoneNumber`}
value={item.phoneNumber}
onChange={(e) => {
const onlyDigits = e.target.value.replace(/[^\d]/g, "").slice(0, 11);
setFieldValue(`referrals.${index}.phoneNumber`, onlyDigits);
}}
fullWidth
placeholder="مثلاً: 0912xxxxxxx"
error={!!itemTouched.phoneNumber && !!itemErrors.phoneNumber}
helperText={itemTouched.phoneNumber ? itemErrors.phoneNumber : ""}
/>
<TextField
label="شغل معرف"
name={`referrals.${index}.jobTitle`}
value={item.jobTitle}
onChange={handleChange}
fullWidth
error={!!itemTouched.jobTitle && !!itemErrors.jobTitle}
helperText={itemTouched.jobTitle ? itemErrors.jobTitle : ""}
/>
<TextField
label="نام محل کار معرف"
name={`referrals.${index}.workplaceName`}
value={item.workplaceName}
onChange={handleChange}
fullWidth
error={!!itemTouched.workplaceName && !!itemErrors.workplaceName}
helperText={itemTouched.workplaceName ? itemErrors.workplaceName : ""}
/>
</Box>
</Paper>
);
}
export default function InnerReferralForm(props: Props) {
const {
values,
errors,
touched,
handleChange,
setFieldValue,
isSubmitting,
} = props;
const handleBack = () => {
props.update({
referrals: values.referrals,
});
props.setStep(props.step - 1);
};
return (
<Form>
<FieldArray name="referrals">
{({ push, remove }) => (
<Paper
elevation={0}
sx={{
p: { xs: 2, md: 3 },
borderRadius: "24px",
border: "1px solid #e2e8f0",
backgroundColor: "#fff",
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: 2,
flexWrap: "wrap",
}}
>
<Typography variant="h6" sx={{ fontWeight: 700 }}>
معرفها
</Typography>
<Button
type="button"
onClick={() =>
push({
...REFERRAL_EMPTY_ITEM,
id: Date.now(),
})
}
variant="contained"
startIcon={<AddIcon />}
sx={{ borderRadius: "12px", fontWeight: 700 }}
>
افزودن معرف جدید
</Button>
</Box>
<Divider sx={{ my: 2 }} />
<Box sx={{ display: "grid", gap: 2 }}>
{values.referrals.map((item, idx) => (
<ReferralItemForm
key={item.id || idx}
index={idx}
item={item}
errors={errors}
touched={touched}
handleChange={handleChange}
setFieldValue={setFieldValue}
onRemove={() => remove(idx)}
disableRemove={values.referrals.length <= REFERRAL_MIN_ITEMS}
/>
))}
</Box>
{typeof errors.referrals === "string" && (
<Typography color="error" sx={{ mt: 2 }}>
{errors.referrals}
</Typography>
)}
<Box
sx={{
display: "flex",
justifyContent: "space-between",
mt: 4,
}}
>
<Button
type="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>
)}
</FieldArray>
</Form>
);
}

View File

@@ -0,0 +1,40 @@
"use client";
import { withFormik, type FormikBag } from "formik";
import type { ReferralFormProps, ReferralFormValues } from "./types";
import { REFERRAL_EMPTY_VALUES } from "./constant";
import { ReferralValidationSchema } from "./validation";
import InnerReferralForm from "./InnerReferralForm";
const ReferralForm = withFormik<ReferralFormProps, ReferralFormValues>({
displayName: "ReferralForm",
enableReinitialize: true,
mapPropsToValues: (props) => {
return {
referrals:
props.data?.referrals?.length > 0
? props.data.referrals
: REFERRAL_EMPTY_VALUES.referrals,
};
},
validationSchema: ReferralValidationSchema,
handleSubmit: async (
values,
bag: FormikBag<ReferralFormProps, ReferralFormValues>,
) => {
const { props, setSubmitting } = bag;
props.update({
referrals: values.referrals,
});
props.setStep((prev) => prev + 1);
setSubmitting(false);
},
})(InnerReferralForm);
export default ReferralForm;

View File

@@ -0,0 +1,19 @@
import type { ReferralFormValues, ReferralItem } from "../types";
export const REFERRAL_EMPTY_ITEM: ReferralItem = {
id: "",
firstName: "",
lastName: "",
relationship: "",
acquaintanceDuration: "",
acquaintanceType: "Direct",
jobTitle: "",
workplaceName: "",
phoneNumber: "",
};
export const REFERRAL_EMPTY_VALUES: ReferralFormValues = {
referrals: [{ ...REFERRAL_EMPTY_ITEM }],
};
export const REFERRAL_MIN_ITEMS = 1;

View File

@@ -0,0 +1,32 @@
import type React from "react";
export type AcquaintanceType = "Direct" | "Indirect";
export interface ReferralItem {
id?: string | number;
firstName: string;
lastName: string;
relationship: string;
acquaintanceDuration: string;
acquaintanceType: AcquaintanceType;
jobTitle: string;
workplaceName: string;
phoneNumber: string;
}
export interface ReferralFormValues {
referrals: ReferralItem[];
}
/** با مدل اصلی wizard پروژه خودت هماهنگش کن */
export interface WizardFormData {
referrals: ReferralItem[];
// ... سایر stepها
}
export interface ReferralFormProps {
step: number;
setStep: React.Dispatch<React.SetStateAction<number>>;
data: WizardFormData;
update: (patch: Partial<WizardFormData>) => void;
}

View File

@@ -0,0 +1,24 @@
import * as Yup from "yup";
export const ReferralValidationSchema = Yup.object({
referrals: Yup.array()
.of(
Yup.object({
id: Yup.mixed().optional(),
firstName: Yup.string().required("نام الزامی است"),
lastName: Yup.string().required("نام خانوادگی الزامی است"),
relationship: Yup.string().required("نسبت / رابطه الزامی است"),
acquaintanceDuration: Yup.string().optional(),
acquaintanceType: Yup.mixed<"Direct" | "Indirect">()
.oneOf(["Direct", "Indirect"], "نوع آشنایی نامعتبر است")
.required("نوع آشنایی الزامی است"),
jobTitle: Yup.string().optional(),
workplaceName: Yup.string().optional(),
phoneNumber: Yup.string()
.required("تلفن تماس الزامی است")
.matches(/^09\d{9}$/, "شماره تماس باید با 09 شروع شده و 11 رقم باشد"),
}),
)
.min(1, "حداقل یک معرف باید ثبت شود")
.required("ثبت معرف الزامی است"),
});