406 lines
11 KiB
TypeScript
406 lines
11 KiB
TypeScript
"use client";
|
||
|
||
import React, { useMemo, useState } from "react";
|
||
import {
|
||
Avatar,
|
||
Box,
|
||
Button,
|
||
MenuItem,
|
||
Paper,
|
||
TextField,
|
||
Typography,
|
||
} from "@mui/material";
|
||
import { UploadFile } from "@mui/icons-material";
|
||
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||
import { AdapterDateFnsJalali } from "@mui/x-date-pickers/AdapterDateFnsJalali";
|
||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||
|
||
type IdentityFormData = {
|
||
applicantId: string;
|
||
firstName: string;
|
||
lastName: string;
|
||
fatherName: string;
|
||
nationalCode: string;
|
||
birthDate: string;
|
||
birthPlace: string;
|
||
gender: string;
|
||
religion: string;
|
||
nationality: string;
|
||
profilePhotoId: string;
|
||
};
|
||
|
||
type IdentityFormErrors = Partial<Record<keyof IdentityFormData, string>>;
|
||
|
||
const initialForm: IdentityFormData = {
|
||
applicantId: "",
|
||
firstName: "",
|
||
lastName: "",
|
||
fatherName: "",
|
||
nationalCode: "",
|
||
birthDate: "",
|
||
birthPlace: "",
|
||
gender: "",
|
||
religion: "",
|
||
nationality: "",
|
||
profilePhotoId: "",
|
||
};
|
||
|
||
export default function IdentityForm() {
|
||
const [formData, setFormData] = useState<IdentityFormData>(initialForm);
|
||
const [errors, setErrors] = useState<IdentityFormErrors>({});
|
||
const [submitted, setSubmitted] = useState(false);
|
||
const [profilePhoto, setProfilePhoto] = useState<File | null>(null);
|
||
const [profilePhotoPreview, setProfilePhotoPreview] = useState<string>("");
|
||
const [profilePhotoError, setProfilePhotoError] = useState<string>("");
|
||
const [birthDateValue, setBirthDateValue] = useState<Date | null>(null);
|
||
|
||
const handleProfilePhotoChange = (
|
||
event: React.ChangeEvent<HTMLInputElement>,
|
||
) => {
|
||
const file = event.target.files?.[0];
|
||
|
||
if (!file) return;
|
||
|
||
if (!file.type.startsWith("image/")) {
|
||
setProfilePhoto(null);
|
||
setProfilePhotoPreview("");
|
||
setProfilePhotoError("فقط فایل تصویری مجاز است");
|
||
return;
|
||
}
|
||
|
||
const maxSize = 500 * 1024; // 500KB
|
||
if (file.size > maxSize) {
|
||
setProfilePhoto(null);
|
||
setProfilePhotoPreview("");
|
||
setProfilePhotoError("حجم عکس باید حداکثر ۵۰۰ کیلوبایت باشد");
|
||
return;
|
||
}
|
||
|
||
setProfilePhoto(file);
|
||
setProfilePhotoError("");
|
||
|
||
const previewUrl = URL.createObjectURL(file);
|
||
setProfilePhotoPreview(previewUrl);
|
||
};
|
||
|
||
const genderOptions = useMemo(() => ["مرد", "زن", "سایر"], []);
|
||
const religionOptions = useMemo(
|
||
() => ["اسلام", "مسیحیت", "یهودیت", "زرتشتی", "سایر"],
|
||
[],
|
||
);
|
||
|
||
const handleChange =
|
||
(field: keyof IdentityFormData) =>
|
||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||
let value = event.target.value;
|
||
|
||
if (field === "nationalCode") {
|
||
value = value.replace(/\D/g, "").slice(0, 10);
|
||
}
|
||
|
||
setFormData((prev) => ({
|
||
...prev,
|
||
[field]: value,
|
||
}));
|
||
|
||
if (errors[field]) {
|
||
setErrors((prev) => ({
|
||
...prev,
|
||
[field]: "",
|
||
}));
|
||
}
|
||
};
|
||
|
||
const validate = () => {
|
||
const newErrors: IdentityFormErrors = {};
|
||
let hasError = false;
|
||
|
||
if (!formData.applicantId.trim()) {
|
||
newErrors.applicantId = "شناسه متقاضی الزامی است";
|
||
hasError = true;
|
||
}
|
||
|
||
if (!formData.firstName.trim()) {
|
||
newErrors.firstName = "نام الزامی است";
|
||
hasError = true;
|
||
}
|
||
|
||
if (!formData.lastName.trim()) {
|
||
newErrors.lastName = "نام خانوادگی الزامی است";
|
||
hasError = true;
|
||
}
|
||
|
||
if (!formData.nationalCode.trim()) {
|
||
newErrors.nationalCode = "کد ملی الزامی است";
|
||
hasError = true;
|
||
} else if (!/^\d{10}$/.test(formData.nationalCode)) {
|
||
newErrors.nationalCode = "کد ملی باید ۱۰ رقم باشد";
|
||
hasError = true;
|
||
}
|
||
|
||
if (!formData.birthDate.trim()) {
|
||
newErrors.birthDate = "تاریخ تولد الزامی است";
|
||
hasError = true;
|
||
}
|
||
|
||
if (!formData.gender.trim()) {
|
||
newErrors.gender = "جنسیت الزامی است";
|
||
hasError = true;
|
||
}
|
||
|
||
if (!formData.nationality.trim()) {
|
||
newErrors.nationality = "ملیت الزامی است";
|
||
hasError = true;
|
||
}
|
||
|
||
if (!profilePhoto) {
|
||
setProfilePhotoError("عکس پرسنلی الزامی است");
|
||
hasError = true;
|
||
} else {
|
||
setProfilePhotoError("");
|
||
}
|
||
|
||
setErrors(newErrors);
|
||
return !hasError;
|
||
};
|
||
|
||
const handleSubmit = (event: React.FormEvent) => {
|
||
event.preventDefault();
|
||
setSubmitted(false);
|
||
|
||
if (!validate()) return;
|
||
|
||
const payload = {
|
||
...formData,
|
||
birthDate: formData.birthDate ? new Date(formData.birthDate) : null,
|
||
fatherName: formData.fatherName || null,
|
||
birthPlace: formData.birthPlace || null,
|
||
religion: formData.religion || null,
|
||
profilePhotoId: formData.profilePhotoId || null,
|
||
};
|
||
|
||
console.log("Identity Payload:", payload);
|
||
setSubmitted(true);
|
||
};
|
||
|
||
return (
|
||
<Box>
|
||
<Paper
|
||
elevation={0}
|
||
sx={{
|
||
width: "100%",
|
||
background: "#ffffff",
|
||
}}
|
||
>
|
||
<Box component="form" onSubmit={handleSubmit}>
|
||
<div
|
||
style={{
|
||
display: "grid",
|
||
gridTemplateColumns: "repeat(auto-fit, minmax(340px, 1fr))",
|
||
gap: "18px",
|
||
}}
|
||
>
|
||
<TextField
|
||
label="نام"
|
||
value={formData.firstName}
|
||
onChange={handleChange("firstName")}
|
||
error={!!errors.firstName}
|
||
helperText={errors.firstName}
|
||
fullWidth
|
||
/>
|
||
|
||
<TextField
|
||
label="نام خانوادگی"
|
||
value={formData.lastName}
|
||
onChange={handleChange("lastName")}
|
||
error={!!errors.lastName}
|
||
helperText={errors.lastName}
|
||
fullWidth
|
||
/>
|
||
|
||
<TextField
|
||
label="نام پدر"
|
||
value={formData.fatherName}
|
||
onChange={handleChange("fatherName")}
|
||
fullWidth
|
||
/>
|
||
|
||
<TextField
|
||
label="کد ملی"
|
||
value={formData.nationalCode}
|
||
onChange={handleChange("nationalCode")}
|
||
error={!!errors.nationalCode}
|
||
helperText={errors.nationalCode}
|
||
fullWidth
|
||
/>
|
||
|
||
<DatePicker
|
||
label="تاریخ تولد"
|
||
value={birthDateValue}
|
||
onChange={(newValue) => {
|
||
setBirthDateValue(newValue);
|
||
|
||
setFormData((prev) => ({
|
||
...prev,
|
||
birthDate: newValue ? newValue.toISOString() : "",
|
||
}));
|
||
|
||
if (errors.birthDate) {
|
||
setErrors((prev) => ({
|
||
...prev,
|
||
birthDate: "",
|
||
}));
|
||
}
|
||
}}
|
||
slotProps={{
|
||
textField: {
|
||
fullWidth: true,
|
||
error: !!errors.birthDate,
|
||
helperText: errors.birthDate,
|
||
},
|
||
}}
|
||
/>
|
||
|
||
<TextField
|
||
label="محل تولد"
|
||
value={formData.birthPlace}
|
||
onChange={handleChange("birthPlace")}
|
||
fullWidth
|
||
/>
|
||
|
||
<TextField
|
||
select
|
||
label="جنسیت"
|
||
value={formData.gender}
|
||
onChange={handleChange("gender")}
|
||
error={!!errors.gender}
|
||
helperText={errors.gender}
|
||
fullWidth
|
||
>
|
||
{genderOptions.map((item) => (
|
||
<MenuItem key={item} value={item}>
|
||
{item}
|
||
</MenuItem>
|
||
))}
|
||
</TextField>
|
||
|
||
<TextField
|
||
select
|
||
label="دین"
|
||
value={formData.religion}
|
||
onChange={handleChange("religion")}
|
||
fullWidth
|
||
>
|
||
{religionOptions.map((item) => (
|
||
<MenuItem key={item} value={item}>
|
||
{item}
|
||
</MenuItem>
|
||
))}
|
||
</TextField>
|
||
|
||
<TextField
|
||
label="ملیت"
|
||
value={formData.nationality}
|
||
onChange={handleChange("nationality")}
|
||
error={!!errors.nationality}
|
||
helperText={errors.nationality}
|
||
fullWidth
|
||
/>
|
||
|
||
<Box
|
||
sx={{
|
||
width: "100%",
|
||
border: profilePhotoError
|
||
? "1px solid #ef4444"
|
||
: "1px dashed #cbd5e1",
|
||
borderRadius: "18px",
|
||
backgroundColor: "#f8fafc",
|
||
p: 2,
|
||
minHeight: "100%",
|
||
transition: "all 0.2s ease",
|
||
"&:hover": {
|
||
borderColor: profilePhotoError ? "#ef4444" : "#2563eb",
|
||
backgroundColor: "#f8fbff",
|
||
},
|
||
}}
|
||
>
|
||
<Typography
|
||
sx={{
|
||
fontWeight: 700,
|
||
color: "#0f172a",
|
||
mb: 1.5,
|
||
fontSize: "0.95rem",
|
||
}}
|
||
>
|
||
عکس پرسنلی
|
||
</Typography>
|
||
|
||
<Typography
|
||
sx={{
|
||
color: "#64748b",
|
||
fontSize: "0.82rem",
|
||
mb: 2,
|
||
lineHeight: 1.8,
|
||
}}
|
||
>
|
||
فقط فایل تصویری مجاز است و حجم آن باید حداکثر ۵۰۰ کیلوبایت باشد.
|
||
</Typography>
|
||
|
||
<Button
|
||
component="label"
|
||
variant="outlined"
|
||
startIcon={<UploadFile />}
|
||
sx={{
|
||
borderRadius: "12px",
|
||
borderColor: "#cbd5e1",
|
||
color: "#2563eb",
|
||
fontWeight: 700,
|
||
px: 2.5,
|
||
"&:hover": {
|
||
borderColor: "#2563eb",
|
||
backgroundColor: "#eff6ff",
|
||
},
|
||
}}
|
||
>
|
||
انتخاب عکس
|
||
<input
|
||
hidden
|
||
type="file"
|
||
accept="image/*"
|
||
onChange={handleProfilePhotoChange}
|
||
/>
|
||
</Button>
|
||
|
||
{profilePhoto && (
|
||
<Typography
|
||
sx={{
|
||
mt: 1.5,
|
||
fontSize: "0.82rem",
|
||
color: "#475569",
|
||
wordBreak: "break-word",
|
||
}}
|
||
>
|
||
فایل انتخابشده: {profilePhoto.name}
|
||
</Typography>
|
||
)}
|
||
|
||
{profilePhotoError && (
|
||
<Typography
|
||
sx={{
|
||
mt: 1.5,
|
||
color: "#dc2626",
|
||
fontSize: "0.8rem",
|
||
fontWeight: 600,
|
||
}}
|
||
>
|
||
{profilePhotoError}
|
||
</Typography>
|
||
)}
|
||
</Box>
|
||
</div>
|
||
</Box>
|
||
</Paper>
|
||
</Box>
|
||
);
|
||
}
|