298 lines
9.0 KiB
TypeScript
298 lines
9.0 KiB
TypeScript
// 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>
|
||
);
|
||
}
|