refactor: enhance form validation and error handling in EditField, DefaultSelect, and Form components

This commit is contained in:
hamed
2025-11-18 09:16:01 +03:30
parent ebc6c246b9
commit 3268b3c51e
6 changed files with 117 additions and 36 deletions
+54 -12
View File
@@ -2,6 +2,21 @@ import EditGrayA from "@/components/icons/EditGrayA";
import EditOrangeA from "@/components/icons/EditOrangeA"; import EditOrangeA from "@/components/icons/EditOrangeA";
import { Button, TextField } from "@mui/material"; import { Button, TextField } from "@mui/material";
// تابع تبدیل اعداد فارسی و عربی به انگلیسی
const convertPersianToEnglish = (str) => {
const persianNumbers = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
const arabicNumbers = ['٠', '١', '٢', '٣', '٤', '٥', '٦', '٧', '٨', '٩'];
let result = str;
for (let i = 0; i < 10; i++) {
const regex = new RegExp(persianNumbers[i], 'g');
result = result.replace(regex, i.toString());
const arabicRegex = new RegExp(arabicNumbers[i], 'g');
result = result.replace(arabicRegex, i.toString());
}
return result;
};
function EditField({ function EditField({
changeData, changeData,
multiline, multiline,
@@ -14,7 +29,31 @@ function EditField({
noDisable, noDisable,
type, type,
props, props,
error,
}) { }) {
const handleChange = (e) => {
let inputValue = e.target.value;
// اگر فیلد کد ملی یا شماره موبایل یا شماره بیمه است، فقط اعداد انگلیسی
if (name === "national_code" || name === "phone" || name === "insurance_id" || type === "tel") {
// ابتدا اعداد فارسی و عربی را به انگلیسی تبدیل کن
inputValue = convertPersianToEnglish(inputValue);
// فقط اعداد انگلیسی را نگه دار
inputValue = inputValue.replace(/[^0-9]/g, '');
// محدودیت تعداد ارقام
if (name === "national_code" && inputValue.length > 10) {
inputValue = inputValue.slice(0, 10);
} else if (name === "phone" && inputValue.length > 11) {
inputValue = inputValue.slice(0, 11);
} else if (name === "insurance_id" && inputValue.length > 16) {
inputValue = inputValue.slice(0, 16);
}
}
changeData(inputValue, "value", name);
};
return ( return (
<div className="relative w-full"> <div className="relative w-full">
<TextField <TextField
@@ -25,12 +64,11 @@ function EditField({
value={data?.value || ""} value={data?.value || ""}
placeholder={placeholder || ""} placeholder={placeholder || ""}
disabled={noDisable ? false : !data?.isEdit} disabled={noDisable ? false : !data?.isEdit}
className={`!w-full res-field-account dark:!bg-transparent ${ error={!!error}
isTransparent ? "!bg-transparent lg:!bg-[#FFF]" : "!bg-[#FFF]" helperText={error || ""}
}`} className={`!w-full res-field-account dark:!bg-transparent ${isTransparent ? "!bg-transparent lg:!bg-[#FFF]" : "!bg-[#FFF]"}
onChange={(e) => { }`}
changeData(e.target.value, "value", name); onChange={handleChange}
}}
sx={{ sx={{
"& .MuiFormLabel-root": { "& .MuiFormLabel-root": {
top: "-4px !important", top: "-4px !important",
@@ -43,6 +81,10 @@ function EditField({
padding: "0 !important", padding: "0 !important",
borderRadius: "8px !important", borderRadius: "8px !important",
}, },
"& .MuiFormHelperText-root": {
marginLeft: "0",
marginRight: "14px",
},
".Mui-focused": { ".Mui-focused": {
"& .MuiOutlinedInput-notchedOutline": { "& .MuiOutlinedInput-notchedOutline": {
border: "1px solid #5559CE !important", border: "1px solid #5559CE !important",
@@ -50,17 +92,17 @@ function EditField({
}, },
}, },
"& input::-webkit-outer-spin-button, & input::-webkit-inner-spin-button": "& input::-webkit-outer-spin-button, & input::-webkit-inner-spin-button":
{ {
display: "none", display: "none",
}, },
}} }}
/> />
<Button <Button
className={`!absolute !left-[8px] !-translate-y-1/2 !min-w-fit !p-1 className={`!absolute !left-[8px] !-translate-y-1/2 !min-w-fit !p-1
${multiline ? "!top-[24px]" : "!top-1/2"}`} ${multiline ? "!top-[24px]" : "!top-1/2"}`}
// onClick={() => { // onClick={() => {
// changeData(!data.isEdit, "isEdit", name); // changeData(!data.isEdit, "isEdit", name);
// }} // }}
> >
{icon ? icon : iconGray ? <EditGrayA /> : <EditOrangeA />} {icon ? icon : iconGray ? <EditGrayA /> : <EditOrangeA />}
</Button> </Button>
+6
View File
@@ -74,8 +74,14 @@ function DefaultSelect({ list, value, updateData, label, error, name }) {
), ),
}} }}
placeholder={label} placeholder={label}
error={!!error}
helperText={error || ""}
sx={{ sx={{
borderRadius: "8px", borderRadius: "8px",
"& .MuiFormHelperText-root": {
marginLeft: "0",
marginRight: "14px",
},
}} }}
/> />
)} )}
+11 -4
View File
@@ -2,7 +2,7 @@ import Content from "./Content";
import EditField from "../../../app/component/fields/EditField"; import EditField from "../../../app/component/fields/EditField";
import DefaultSelect from "@/app/component/selectors/DefaultSelect"; import DefaultSelect from "@/app/component/selectors/DefaultSelect";
function Form({ changeData, insurance, supplementaryInsurance, data, isForAnother }) { function Form({ changeData, insurance, supplementaryInsurance, data, isForAnother, errors = {} }) {
const findBasicInsuranceVal = () => { const findBasicInsuranceVal = () => {
return insurance?.find( return insurance?.find(
(g) => (g) =>
@@ -25,20 +25,21 @@ function Form({ changeData, insurance, supplementaryInsurance, data, isForAnothe
justify-center gap-x-[24px] gap-y-[12px] md:gap-y-[14px] lg:gap-y-[16px] grid-cols-1 sm:grid-cols-2" justify-center gap-x-[24px] gap-y-[12px] md:gap-y-[14px] lg:gap-y-[16px] grid-cols-1 sm:grid-cols-2"
> >
<Content title="شماره موبایل" isConfirmed={isForAnother ? false : true}> <Content title="شماره موبایل" isConfirmed={isForAnother ? false : true}>
<EditField changeData={changeData} data={data?.phone} name="phone" /> <EditField changeData={changeData} data={data?.phone} name="phone" error={errors?.phone} />
</Content> </Content>
<Content title="کد ملی"> <Content title="کد ملی">
<EditField <EditField
changeData={changeData} changeData={changeData}
data={data?.national_code} data={data?.national_code}
name="national_code" name="national_code"
error={errors?.national_code}
/> />
</Content> </Content>
<Content title="نام"> <Content title="نام">
<EditField changeData={changeData} data={data?.name} name="name" /> <EditField changeData={changeData} data={data?.name} name="name" error={errors?.name} />
</Content> </Content>
<Content title="نام خانوادگی"> <Content title="نام خانوادگی">
<EditField changeData={changeData} data={data?.family} name="family" /> <EditField changeData={changeData} data={data?.family} name="family" error={errors?.family} />
</Content> </Content>
<Content title="جنسیت"> <Content title="جنسیت">
<DefaultSelect <DefaultSelect
@@ -54,8 +55,12 @@ function Form({ changeData, insurance, supplementaryInsurance, data, isForAnothe
? { id: data.gender.value, title: data.gender.value === "male" ? "مرد" : "زن" } ? { id: data.gender.value, title: data.gender.value === "male" ? "مرد" : "زن" }
: null : null
} }
error={errors?.gender}
/> />
</Content> </Content>
<Content title="شماره بیمه">
<EditField changeData={changeData} data={data?.insurance_id} name="insurance_id" error={errors?.insurance_id} />
</Content>
<Content title="نوع بیمه"> <Content title="نوع بیمه">
<DefaultSelect <DefaultSelect
updateData={(name, val) => changeData(val, "value", name)} updateData={(name, val) => changeData(val, "value", name)}
@@ -63,6 +68,7 @@ function Form({ changeData, insurance, supplementaryInsurance, data, isForAnothe
name="basic_insurance" name="basic_insurance"
list={insurance} list={insurance}
value={findBasicInsuranceVal()} value={findBasicInsuranceVal()}
error={errors?.basic_insurance}
/> />
</Content> </Content>
<Content title="بیمه تکمیلی"> <Content title="بیمه تکمیلی">
@@ -72,6 +78,7 @@ function Form({ changeData, insurance, supplementaryInsurance, data, isForAnothe
name="supplementary_insurance" name="supplementary_insurance"
list={supplementaryInsurance} list={supplementaryInsurance}
value={findSupplementaryInsuranceVal()} value={findSupplementaryInsuranceVal()}
error={errors?.supplementary_insurance}
/> />
</Content> </Content>
</div> </div>
+35 -19
View File
@@ -4,35 +4,51 @@ import { Button } from "@mui/material";
import ArrowLeftB from "@/components/icons/ArrowLeftB"; import ArrowLeftB from "@/components/icons/ArrowLeftB";
import { request } from "@/services/response"; import { request } from "@/services/response";
function SubmitData({ setStep, data, prevData }) { function SubmitData({ setStep, data, prevData, setErrors }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const newStep = () => setStep((prev) => prev + 1); const newStep = () => setStep((prev) => prev + 1);
const handleSubmit = () => { const handleSubmit = async () => {
const isChanged = JSON.stringify(prevData) !== JSON.stringify(data); const isChanged = JSON.stringify(prevData) !== JSON.stringify(data);
// پاک کردن error های قبلی
setErrors({});
if (isChanged) { if (isChanged) {
setLoading(true); setLoading(true);
let newData = data; const { basic_insurance, supplementary_insurance, name, family, national_code, gender, insurance_id, uuid } = data;
const { basic_insurance, name, national_code, uuid } = newData;
newData = { const payload = {
basic_insurance: [basic_insurance.value?.id], name: name?.value,
...(prevData.name?.value === data.name.value ? {} : { name: name }), family: family?.value,
...(prevData.national_code?.value national_code: national_code?.value,
? {} gender: gender?.value,
: { national_code: national_code }), insurance_id: insurance_id?.value,
basic_insurance: basic_insurance?.value?.id ? [basic_insurance.value.id] : [],
supplementary_insurance: supplementary_insurance?.value?.id ? [supplementary_insurance.value.id] : [],
}; };
request try {
.patchUserProfile(newData, uuid) if (uuid) {
.then(() => { // اگر uuid داریم، پروفایل وجود داره و باید PATCH کنیم
setLoading(false); console.log('📤 ارسال درخواست PATCH - UUID:', uuid, 'Payload:', payload);
newStep(); await request.patchUserProfile(payload, uuid);
}) } else {
.catch(() => { // اگر uuid نداریم، پروفایل وجود نداره و باید POST کنیم
setLoading(false); console.log('📤 ارسال درخواست POST - Payload:', payload);
}); await request.postUserProfile(payload);
}
console.log('✅ پروفایل با موفقیت ذخیره شد');
setLoading(false);
newStep();
} catch (error) {
console.log('❌ خطا در ذخیره پروفایل:', error?.response?.data || error);
setLoading(false);
// اگر خطای validation بود، error ها را ست می‌کنیم
if (error?.response?.data) {
setErrors(error.response.data);
}
}
} else { } else {
newStep(); newStep();
} }
+4 -1
View File
@@ -18,6 +18,8 @@ function Detail({
}) { }) {
const [insurance, setInsurance] = useState(); const [insurance, setInsurance] = useState();
const [supplementaryInsurance, setSupplementaryInsurance] = useState(); const [supplementaryInsurance, setSupplementaryInsurance] = useState();
const [errors, setErrors] = useState({});
const changeData = (value, type, name) => const changeData = (value, type, name) =>
setData({ setData({
...data, ...data,
@@ -68,6 +70,7 @@ function Detail({
supplementaryInsurance={supplementaryInsurance} supplementaryInsurance={supplementaryInsurance}
changeData={changeData} changeData={changeData}
isForAnother={isForAnother} isForAnother={isForAnother}
errors={errors}
/> />
{!isForAnother && ( {!isForAnother && (
<Button <Button
@@ -87,7 +90,7 @@ function Detail({
</p> </p>
</Button> </Button>
)} )}
<SubmitData setStep={setStep} data={data} prevData={prevData} /> <SubmitData setStep={setStep} data={data} prevData={prevData} setErrors={setErrors} />
</div> </div>
</div> </div>
); );
+7
View File
@@ -11,6 +11,7 @@ const defaultData = {
name: { value: "", isEdit: true }, name: { value: "", isEdit: true },
family: { value: "", isEdit: true }, family: { value: "", isEdit: true },
gender: { value: "", isEdit: true }, gender: { value: "", isEdit: true },
insurance_id: { value: "", isEdit: true },
basic_insurance: { value: "", isEdit: true }, basic_insurance: { value: "", isEdit: true },
}; };
@@ -50,6 +51,7 @@ function AppointmentPage({ doctor, disabledDates, matchedCity }) {
// سپس اگر uuid داریم، بقیه اطلاعات را از API می‌گیریم // سپس اگر uuid داریم، بقیه اطلاعات را از API می‌گیریم
if (userInfo && parsedData) { if (userInfo && parsedData) {
try { try {
console.log('📤 درخواست دریافت پروفایل کاربر - UUID:', parsedData.uuid);
const res = await request.getUserProfile(parsedData.uuid); const res = await request.getUserProfile(parsedData.uuid);
if (res) { if (res) {
@@ -63,6 +65,7 @@ function AppointmentPage({ doctor, disabledDates, matchedCity }) {
name: { value: res.name || "", isEdit: true }, name: { value: res.name || "", isEdit: true },
family: { value: res.family || "", isEdit: true }, family: { value: res.family || "", isEdit: true },
gender: { value: res.gender || "", isEdit: true }, gender: { value: res.gender || "", isEdit: true },
insurance_id: { value: res.insurance_id || "", isEdit: true },
basic_insurance: { basic_insurance: {
value: res.basic_insurance, value: res.basic_insurance,
isEdit: true, isEdit: true,
@@ -80,6 +83,7 @@ function AppointmentPage({ doctor, disabledDates, matchedCity }) {
name: { value: "", isEdit: true }, name: { value: "", isEdit: true },
family: { value: "", isEdit: true }, family: { value: "", isEdit: true },
gender: { value: "", isEdit: true }, gender: { value: "", isEdit: true },
insurance_id: { value: "", isEdit: true },
basic_insurance: { value: "", isEdit: true }, basic_insurance: { value: "", isEdit: true },
})); }));
} }
@@ -104,6 +108,7 @@ function AppointmentPage({ doctor, disabledDates, matchedCity }) {
if (userInfo && parsedData && !data.uuid) { if (userInfo && parsedData && !data.uuid) {
try { try {
console.log('📤 درخواست مجدد دریافت پروفایل (step 3) - UUID:', parsedData.uuid);
const res = await request.getUserProfile(parsedData.uuid); const res = await request.getUserProfile(parsedData.uuid);
if (res) { if (res) {
@@ -117,6 +122,7 @@ function AppointmentPage({ doctor, disabledDates, matchedCity }) {
name: { value: res.name || "", isEdit: true }, name: { value: res.name || "", isEdit: true },
family: { value: res.family || "", isEdit: true }, family: { value: res.family || "", isEdit: true },
gender: { value: res.gender || "", isEdit: true }, gender: { value: res.gender || "", isEdit: true },
insurance_id: { value: res.insurance_id || "", isEdit: true },
basic_insurance: { basic_insurance: {
value: res.basic_insurance, value: res.basic_insurance,
isEdit: true, isEdit: true,
@@ -134,6 +140,7 @@ function AppointmentPage({ doctor, disabledDates, matchedCity }) {
name: { value: "", isEdit: true }, name: { value: "", isEdit: true },
family: { value: "", isEdit: true }, family: { value: "", isEdit: true },
gender: { value: "", isEdit: true }, gender: { value: "", isEdit: true },
insurance_id: { value: "", isEdit: true },
basic_insurance: { value: "", isEdit: true }, basic_insurance: { value: "", isEdit: true },
})); }));
} }