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,297 @@
// InnerPhysicalInfoForm.tsx
"use client";
import React, { useEffect, useMemo } from "react";
import { Box, Button, MenuItem, TextField } from "@mui/material";
import { Form, type FormikProps } from "formik";
import { PhysicalInfoFormValues } from "./types";
import { PhysicalInfoFormProps } from "./PhysicalInfoForm";
import { BLOOD_TYPE_OPTIONS } from "./constants";
import { handleBack } from "@/core/utils";
type Props = FormikProps<PhysicalInfoFormValues> & PhysicalInfoFormProps;
function round1(n: number) {
return Math.round(n * 10) / 10;
}
export default function InnerPhysicalInfoForm(props: Props) {
const { values, errors, touched, setFieldValue, handleChange } = props;
const computedBmi = useMemo(() => {
if (values.height === "" || values.weight === "") return "";
const hMeters = Number(values.height) / 100;
if (!hMeters || hMeters <= 0) return "";
const bmi = Number(values.weight) / (hMeters * hMeters);
return Number.isFinite(bmi) ? round1(bmi) : "";
}, [values.height, values.weight]);
useEffect(() => {
if (values.bmi !== computedBmi) {
setFieldValue("bmi", computedBmi, false);
}
}, [computedBmi, setFieldValue, values.bmi]);
const tf = <K extends keyof PhysicalInfoFormValues>(name: K) => ({
name: String(name),
value: values[name] as any,
onChange: handleChange,
fullWidth: true,
error: !!(touched as any)[name] && !!(errors as any)[name],
helperText: (touched as any)[name] ? ((errors as any)[name] as string) : "",
});
console.log(props.errors)
return (
<Form>
<Box
sx={{
display: "grid",
gridTemplateColumns: { xs: "1fr", md: "repeat(2, 1fr)" },
gap: 2,
width: "100%",
}}
>
{/* bloodType */}
<TextField
select
label="گروه خونی"
name="bloodType"
value={values.bloodType}
onChange={handleChange}
fullWidth
error={!!touched.bloodType && !!errors.bloodType}
helperText={touched.bloodType ? (errors.bloodType as string) : ""}
>
<MenuItem value="">انتخاب کنید</MenuItem>
{BLOOD_TYPE_OPTIONS.map((bt) => (
<MenuItem key={bt} value={bt}>
{bt}
</MenuItem>
))}
</TextField>
{/* height */}
<TextField
label="قد (سانتی‌متر)"
name="height"
type="number"
value={values.height}
onChange={(e) =>
setFieldValue(
"height",
e.target.value === "" ? "" : Number(e.target.value),
)
}
fullWidth
error={!!touched.height && !!errors.height}
helperText={touched.height ? (errors.height as string) : ""}
/>
{/* weight */}
<TextField
label="وزن (کیلوگرم)"
name="weight"
type="number"
value={values.weight}
onChange={(e) =>
setFieldValue(
"weight",
e.target.value === "" ? "" : Number(e.target.value),
)
}
fullWidth
error={!!touched.weight && !!errors.weight}
helperText={touched.weight ? (errors.weight as string) : ""}
/>
{/* bmi */}
<TextField
label="BMI"
name="bmi"
type="number"
value={values.bmi}
fullWidth
disabled
error={!!touched.bmi && !!errors.bmi}
helperText={
touched.bmi && errors.bmi
? (errors.bmi as string)
: values.height !== "" && values.weight !== ""
? "به‌صورت خودکار از قد و وزن محاسبه می‌شود"
: "برای محاسبه BMI، قد و وزن را وارد کنید"
}
/>
{/* specialMark */}
<TextField label="علامت مشخصه" {...tf("specialMark")} />
{/* hasDisability */}
<TextField
select
label="معلولیت دارد؟"
name="hasDisability"
value={String(values.hasDisability)}
onChange={(e) => {
const next = e.target.value === "true";
setFieldValue("hasDisability", next);
if (!next) {
setFieldValue("disabilityDescription", "");
}
}}
fullWidth
error={!!touched.hasDisability && !!errors.hasDisability}
helperText={
touched.hasDisability ? (errors.hasDisability as string) : ""
}
>
<MenuItem value="false">خیر</MenuItem>
<MenuItem value="true">بله</MenuItem>
</TextField>
{/* hasChronicDisease */}
<TextField
select
label="بیماری مزمن دارد؟"
name="hasChronicDisease"
value={String(values.hasChronicDisease)}
onChange={(e) => {
const next = e.target.value === "true";
setFieldValue("hasChronicDisease", next);
if (!next) {
setFieldValue("chronicDiseaseDescription", "");
}
}}
fullWidth
error={!!touched.hasChronicDisease && !!errors.hasChronicDisease}
helperText={
touched.hasChronicDisease
? (errors.hasChronicDisease as string)
: ""
}
>
<MenuItem value="false">خیر</MenuItem>
<MenuItem value="true">بله</MenuItem>
</TextField>
{/* disabilityDescription */}
{values.hasDisability && (
<Box sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}>
<TextField
label="توضیحات معلولیت"
name="disabilityDescription"
value={values.disabilityDescription}
onChange={handleChange}
fullWidth
multiline
minRows={2}
error={
!!touched.disabilityDescription &&
!!errors.disabilityDescription
}
helperText={
touched.disabilityDescription
? (errors.disabilityDescription as string)
: ""
}
/>
</Box>
)}
{/* chronicDiseaseDescription */}
{values.hasChronicDisease && (
<Box sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}>
<TextField
label="توضیحات بیماری مزمن"
name="chronicDiseaseDescription"
value={values.chronicDiseaseDescription}
onChange={handleChange}
fullWidth
multiline
minRows={2}
error={
!!touched.chronicDiseaseDescription &&
!!errors.chronicDiseaseDescription
}
helperText={
touched.chronicDiseaseDescription
? (errors.chronicDiseaseDescription as string)
: ""
}
/>
</Box>
)}
{/* surgeryHistory */}
<Box sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}>
<TextField
label="سابقه جراحی"
name="surgeryHistory"
value={values.surgeryHistory}
onChange={handleChange}
fullWidth
multiline
minRows={2}
error={!!touched.surgeryHistory && !!errors.surgeryHistory}
helperText={
touched.surgeryHistory ? (errors.surgeryHistory as string) : ""
}
/>
</Box>
{/* medications */}
<Box sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}>
<TextField
label="داروهای مصرفی"
name="medications"
value={values.medications}
onChange={handleChange}
fullWidth
multiline
minRows={2}
error={!!touched.medications && !!errors.medications}
helperText={
touched.medications ? (errors.medications as string) : ""
}
/>
</Box>
</Box>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
mt: 5,
width: "100%",
}}
>
<Button
disabled={props.step === 1}
type="button"
onClick={() => handleBack(props,"physicalInfo")}
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>
</Form>
);
}

View File

@@ -0,0 +1,52 @@
// PhysicalInfoForm.tsx
"use client";
import React from "react";
import { withFormik, type FormikBag } from "formik";
import InnerPhysicalInfoForm from "./InnerPhysicalInfoForm";
import { PhysicalInfoFormValues } from "./types";
import { PHYSICAL_INFO_EMPTY_VALUES } from "./constants";
import { PhysicalInfoValidationSchema } from "./validation";
/** این بخش را با ساختار اصلی WizardFormData پروژه خودت هماهنگ کن */
export interface WizardFormData {
physicalInfo: PhysicalInfoFormValues;
// ... بقیه step ها
}
export type PhysicalInfoFormProps = {
step: number;
setStep: React.Dispatch<React.SetStateAction<number>>;
data: WizardFormData;
update: (patch: Partial<WizardFormData>) => void;
};
const PhysicalInfoForm = withFormik<
PhysicalInfoFormProps,
PhysicalInfoFormValues
>({
displayName: "PhysicalInfoForm",
enableReinitialize: true,
mapPropsToValues: (props) => {
return props.data?.physicalInfo ?? PHYSICAL_INFO_EMPTY_VALUES;
},
// validationSchema: PhysicalInfoValidationSchema,
handleSubmit: async (
values,
bag: FormikBag<PhysicalInfoFormProps, PhysicalInfoFormValues>,
) => {
const { props, setSubmitting } = bag;
props.update({ physicalInfo: values });
props.setStep((prev) => prev + 1);
setSubmitting(false);
},
})(InnerPhysicalInfoForm);
export default PhysicalInfoForm;

View File

@@ -0,0 +1,35 @@
// physical-info.constants.ts
import { BloodType, PhysicalInfoFormValues } from "../types";
export const PHYSICAL_INFO_EMPTY_VALUES: PhysicalInfoFormValues = {
applicantId: "",
bloodType: "",
height: "",
weight: "",
bmi: "",
hasDisability: false,
disabilityDescription: "",
hasChronicDisease: false,
chronicDiseaseDescription: "",
surgeryHistory: "",
medications: "",
specialMark: "",
};
export const BLOOD_TYPE_OPTIONS: Exclude<BloodType, "">[] = [
"A+",
"A-",
"B+",
"B-",
"AB+",
"AB-",
"O+",
"O-",
];

View File

@@ -0,0 +1,32 @@
// physical-info.types.ts
export type BloodType =
| ""
| "A+"
| "A-"
| "B+"
| "B-"
| "AB+"
| "AB-"
| "O+"
| "O-";
export interface PhysicalInfoFormValues {
applicantId: string;
bloodType: BloodType;
height: number | ""; // cm
weight: number | ""; // kg
bmi: number | ""; // auto calculated
hasDisability: boolean;
disabilityDescription: string;
hasChronicDisease: boolean;
chronicDiseaseDescription: string;
surgeryHistory: string;
medications: string;
specialMark: string;
}

View File

@@ -0,0 +1,68 @@
// PhysicalInfoForm.validation.ts
import * as yup from "yup";
import { PhysicalInfoFormValues } from "../types";
export const PhysicalInfoValidationSchema =
yup
.object({
// applicantId: yup.string().trim().required("کد متقاضی الزامی است"),
bloodType: yup
.mixed<PhysicalInfoFormValues["bloodType"]>()
.oneOf(["", "A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"])
.required("گروه خونی را انتخاب کنید"),
height: yup
.mixed<number | "">()
.test(
"height-valid",
"قد نامعتبر است",
(v) => v === "" || (typeof v === "number" && v > 0 && v <= 300),
)
.required("قد الزامی است"),
weight: yup
.mixed<number | "">()
.test(
"weight-valid",
"وزن نامعتبر است",
(v) => v === "" || (typeof v === "number" && v > 0 && v <= 500),
)
.required("وزن الزامی است"),
bmi: yup
.mixed<number | "">()
.test(
"bmi-valid",
"BMI نامعتبر است يا قد و وزن اشتباه است",
(v) => v === "" || (typeof v === "number" && v > 0 && v <= 100),
)
.notRequired(),
hasDisability: yup.boolean().required(),
disabilityDescription: yup
.string()
.trim()
.when("hasDisability", {
is: true,
then: (s) => s.required("توضیحات معلولیت الزامی است"),
otherwise: (s) => s.notRequired(),
}),
hasChronicDisease: yup.boolean().required(),
chronicDiseaseDescription: yup
.string()
.trim()
.when("hasChronicDisease", {
is: true,
then: (s) => s.required("توضیحات بیماری مزمن الزامی است"),
otherwise: (s) => s.notRequired(),
}),
surgeryHistory: yup.string().trim().notRequired(),
medications: yup.string().trim().notRequired(),
specialMark: yup.string().trim().notRequired(),
})
.required();