first commit
This commit is contained in:
286
ui/forms/PhysicalInfoForm.tsx
Normal file
286
ui/forms/PhysicalInfoForm.tsx
Normal file
@@ -0,0 +1,286 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user