change some files
This commit is contained in:
320
ui/forms/referral/InnerReferralForm.tsx
Normal file
320
ui/forms/referral/InnerReferralForm.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
40
ui/forms/referral/ReferralForm.tsx
Normal file
40
ui/forms/referral/ReferralForm.tsx
Normal 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;
|
||||
19
ui/forms/referral/constant/index.ts
Normal file
19
ui/forms/referral/constant/index.ts
Normal 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;
|
||||
32
ui/forms/referral/types/index.ts
Normal file
32
ui/forms/referral/types/index.ts
Normal 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;
|
||||
}
|
||||
24
ui/forms/referral/validation/index.ts
Normal file
24
ui/forms/referral/validation/index.ts
Normal 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("ثبت معرف الزامی است"),
|
||||
});
|
||||
Reference in New Issue
Block a user