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