287 lines
7.8 KiB
TypeScript
287 lines
7.8 KiB
TypeScript
import React, { useEffect, useMemo, useState } from "react";
|
||
import { Box, MenuItem, TextField } from "@mui/material";
|
||
|
||
type BloodType = "" | "A+" | "A-" | "B+" | "B-" | "AB+" | "AB-" | "O+" | "O-";
|
||
|
||
export interface PhysicalInfoFormState {
|
||
applicantId: string;
|
||
|
||
bloodType: BloodType;
|
||
height: number | ""; // cm
|
||
weight: number | ""; // kg
|
||
bmi: number | ""; // auto or manual
|
||
|
||
hasDisability: boolean;
|
||
disabilityDescription: string;
|
||
|
||
hasChronicDisease: boolean;
|
||
chronicDiseaseDescription: string;
|
||
|
||
surgeryHistory: string;
|
||
medications: string;
|
||
|
||
specialMark: string;
|
||
}
|
||
|
||
const initialValues: PhysicalInfoFormState = {
|
||
applicantId: "",
|
||
|
||
bloodType: "",
|
||
height: "",
|
||
weight: "",
|
||
bmi: "",
|
||
|
||
hasDisability: false,
|
||
disabilityDescription: "",
|
||
|
||
hasChronicDisease: false,
|
||
chronicDiseaseDescription: "",
|
||
|
||
surgeryHistory: "",
|
||
medications: "",
|
||
|
||
specialMark: "",
|
||
};
|
||
|
||
function toNumberOrEmpty(v: string): number | "" {
|
||
if (v === "") return "";
|
||
const n = Number(v);
|
||
return Number.isFinite(n) ? n : "";
|
||
}
|
||
|
||
function round1(n: number) {
|
||
return Math.round(n * 10) / 10;
|
||
}
|
||
|
||
export default function PhysicalInfoForm(props: {
|
||
value?: PhysicalInfoFormState;
|
||
onChange?: (next: PhysicalInfoFormState) => void;
|
||
applicantId?: string; // اگر خواستی از بیرون تزریق کنی
|
||
}) {
|
||
const { value, onChange, applicantId } = props;
|
||
|
||
const [formData, setFormData] = useState<PhysicalInfoFormState>(
|
||
value ?? { ...initialValues, applicantId: applicantId ?? "" },
|
||
);
|
||
|
||
// اگر value کنترلشده بود، همگامسازی
|
||
useEffect(() => {
|
||
if (value) setFormData(value);
|
||
}, [value]);
|
||
|
||
// اگر applicantId از بیرون تغییر کرد
|
||
useEffect(() => {
|
||
if (!value && applicantId) {
|
||
setFormData((p) => ({ ...p, applicantId }));
|
||
}
|
||
}, [applicantId, value]);
|
||
|
||
const setNext = (
|
||
updater: (prev: PhysicalInfoFormState) => PhysicalInfoFormState,
|
||
) => {
|
||
setFormData((prev) => {
|
||
const next = updater(prev);
|
||
onChange?.(next);
|
||
return next;
|
||
});
|
||
};
|
||
|
||
const handleText =
|
||
(field: keyof PhysicalInfoFormState) =>
|
||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const v = e.target.value;
|
||
setNext((p) => ({ ...p, [field]: v }) as PhysicalInfoFormState);
|
||
};
|
||
|
||
const handleNumber =
|
||
(field: keyof PhysicalInfoFormState) =>
|
||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const v = toNumberOrEmpty(e.target.value);
|
||
setNext((p) => ({ ...p, [field]: v }) as PhysicalInfoFormState);
|
||
};
|
||
|
||
const handleBoolSelect =
|
||
(field: "hasDisability" | "hasChronicDisease") =>
|
||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||
const v = e.target.value === "true";
|
||
setNext((p) => {
|
||
// اگر false شد، توضیحات را پاک میکنیم تا داده کثیف نماند
|
||
if (field === "hasDisability" && !v) {
|
||
return { ...p, hasDisability: false, disabilityDescription: "" };
|
||
}
|
||
if (field === "hasChronicDisease" && !v) {
|
||
return {
|
||
...p,
|
||
hasChronicDisease: false,
|
||
chronicDiseaseDescription: "",
|
||
};
|
||
}
|
||
return { ...p, [field]: v };
|
||
});
|
||
};
|
||
|
||
// محاسبه BMI از روی قد و وزن (cm, kg)
|
||
const computedBmi = useMemo(() => {
|
||
if (formData.height === "" || formData.weight === "") return "";
|
||
const hMeters = Number(formData.height) / 100;
|
||
if (!hMeters || hMeters <= 0) return "";
|
||
const bmi = Number(formData.weight) / (hMeters * hMeters);
|
||
return Number.isFinite(bmi) ? round1(bmi) : "";
|
||
}, [formData.height, formData.weight]);
|
||
|
||
// sync bmi (فقط وقتی قد/وزن داریم)
|
||
useEffect(() => {
|
||
// اگر بخوای دستی BMI وارد کنی، این بخش رو حذف کن.
|
||
setNext((p) => ({ ...p, bmi: computedBmi }));
|
||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
}, [computedBmi]);
|
||
|
||
return (
|
||
<Box
|
||
sx={{
|
||
display: "grid",
|
||
gridTemplateColumns: { xs: "1fr", md: "repeat(2, 1fr)" },
|
||
gap: 2,
|
||
width: "100%",
|
||
}}
|
||
>
|
||
|
||
|
||
{/* bloodType */}
|
||
<TextField
|
||
select
|
||
label="گروه خونی"
|
||
value={formData.bloodType}
|
||
onChange={handleText("bloodType")}
|
||
fullWidth
|
||
>
|
||
<MenuItem value="">انتخاب کنید</MenuItem>
|
||
{(["A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"] as const).map(
|
||
(bt) => (
|
||
<MenuItem key={bt} value={bt}>
|
||
{bt}
|
||
</MenuItem>
|
||
),
|
||
)}
|
||
</TextField>
|
||
|
||
{/* height */}
|
||
<TextField
|
||
label="قد (سانتیمتر)"
|
||
type="number"
|
||
value={formData.height}
|
||
onChange={handleNumber("height")}
|
||
fullWidth
|
||
/>
|
||
|
||
{/* weight */}
|
||
<TextField
|
||
label="وزن (کیلوگرم)"
|
||
type="number"
|
||
value={formData.weight}
|
||
onChange={handleNumber("weight")}
|
||
fullWidth
|
||
/>
|
||
|
||
{/* bmi */}
|
||
<TextField
|
||
label="BMI"
|
||
type="number"
|
||
value={formData.bmi}
|
||
onChange={handleNumber("bmi")}
|
||
fullWidth
|
||
disabled // چون خودکار محاسبه میکنیم
|
||
helperText={
|
||
formData.height !== "" && formData.weight !== ""
|
||
? "بهصورت خودکار از قد و وزن محاسبه میشود"
|
||
: "برای محاسبه BMI، قد و وزن را وارد کنید"
|
||
}
|
||
/>
|
||
|
||
{/* specialMark */}
|
||
<TextField
|
||
label="علامت مشخصه"
|
||
value={formData.specialMark}
|
||
onChange={handleText("specialMark")}
|
||
fullWidth
|
||
/>
|
||
|
||
{/* hasDisability */}
|
||
<TextField
|
||
select
|
||
label="معلولیت دارد؟"
|
||
value={String(formData.hasDisability)}
|
||
onChange={handleBoolSelect("hasDisability")}
|
||
fullWidth
|
||
>
|
||
<MenuItem value="false">خیر</MenuItem>
|
||
<MenuItem value="true">بله</MenuItem>
|
||
</TextField>
|
||
|
||
{/* hasChronicDisease */}
|
||
<TextField
|
||
select
|
||
label="بیماری مزمن دارد؟"
|
||
value={String(formData.hasChronicDisease)}
|
||
onChange={handleBoolSelect("hasChronicDisease")}
|
||
fullWidth
|
||
>
|
||
<MenuItem value="false">خیر</MenuItem>
|
||
<MenuItem value="true">بله</MenuItem>
|
||
</TextField>
|
||
|
||
{/* disabilityDescription */}
|
||
{formData.hasDisability && (
|
||
<Box sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}>
|
||
<TextField
|
||
label="توضیحات معلولیت"
|
||
value={formData.disabilityDescription}
|
||
onChange={handleText("disabilityDescription")}
|
||
fullWidth
|
||
multiline
|
||
minRows={2}
|
||
/>
|
||
</Box>
|
||
)}
|
||
|
||
{/* chronicDiseaseDescription */}
|
||
{formData.hasChronicDisease && (
|
||
<Box sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}>
|
||
<TextField
|
||
label="توضیحات بیماری مزمن"
|
||
value={formData.chronicDiseaseDescription}
|
||
onChange={handleText("chronicDiseaseDescription")}
|
||
fullWidth
|
||
multiline
|
||
minRows={2}
|
||
/>
|
||
</Box>
|
||
)}
|
||
|
||
{/* surgeryHistory */}
|
||
<Box sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}>
|
||
<TextField
|
||
label="سابقه جراحی"
|
||
value={formData.surgeryHistory}
|
||
onChange={handleText("surgeryHistory")}
|
||
fullWidth
|
||
multiline
|
||
minRows={2}
|
||
/>
|
||
</Box>
|
||
|
||
{/* medications */}
|
||
<Box sx={{ gridColumn: { xs: "1", md: "1 / -1" } }}>
|
||
<TextField
|
||
label="داروهای مصرفی"
|
||
value={formData.medications}
|
||
onChange={handleText("medications")}
|
||
fullWidth
|
||
multiline
|
||
minRows={2}
|
||
/>
|
||
</Box>
|
||
</Box>
|
||
);
|
||
}
|