774 lines
31 KiB
JavaScript
774 lines
31 KiB
JavaScript
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
import { useDispatch, useSelector } from "react-redux";
|
|
import { updatePersonalDetails, clearAllStepsFrom } from "../redux/registrationFormSlice";
|
|
import {
|
|
Grid,
|
|
TextField,
|
|
FormControl,
|
|
InputLabel,
|
|
Select,
|
|
MenuItem,
|
|
Button,
|
|
Box,
|
|
Typography,
|
|
InputAdornment,
|
|
IconButton,
|
|
FormHelperText,
|
|
CircularProgress,
|
|
Autocomplete,
|
|
} from "@mui/material";
|
|
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
|
|
import Visibility from "@mui/icons-material/Visibility";
|
|
import VisibilityOff from "@mui/icons-material/VisibilityOff";
|
|
import AdvancedDropzone from "./AdvancedDropzone";
|
|
import toast from "react-hot-toast";
|
|
|
|
import {
|
|
usePersonalDetailsMasters,
|
|
useCasteMasters,
|
|
useSubCasteMasters,
|
|
} from "../hooks/useDependentMasters";
|
|
import { useSendOtp, useVerifyOtp } from "../hooks/useAuth";
|
|
|
|
const OTP_LENGTH = 4;
|
|
const OTP_TIMER_SEC = 60;
|
|
|
|
const PersonalDetailsForm = ({ onSubmitStep, errors: externalErrors, onFieldChange, isEditMode }) => {
|
|
const dispatch = useDispatch();
|
|
const data = useSelector((state) => state.registerform.personalDetails);
|
|
const nameInputRef = useRef(null);
|
|
const mobileInputRef = useRef(null);
|
|
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
|
|
|
const [showOtp, setShowOtp] = useState(false);
|
|
const [otp, setOtp] = useState(new Array(OTP_LENGTH).fill(""));
|
|
const [otpTimer, setOtpTimer] = useState(0);
|
|
const [otpError, setOtpError] = useState("");
|
|
const [mobileOtpVerified, setMobileOtpVerified] = useState(false);
|
|
const [mobileNumberError, setMobileNumberError] = useState("");
|
|
const [showPassword, setShowPassword] = useState(false);
|
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
const [localErrors, setLocalErrors] = useState({});
|
|
|
|
const sendOtp = useSendOtp();
|
|
const verifyOtp = useVerifyOtp();
|
|
|
|
const errors = useMemo(() => ({ ...externalErrors, ...localErrors }), [externalErrors, localErrors]);
|
|
|
|
const { data: personalMasters, isLoading: isPersonalMastersLoading } = usePersonalDetailsMasters();
|
|
const casteQuery = useCasteMasters(data.religion);
|
|
const subCasteQuery = useSubCasteMasters(data.caste);
|
|
|
|
const genderOptions = useMemo(() => personalMasters?.gender ?? ["Male", "Female"], [personalMasters]);
|
|
const maritalStatusOptions = useMemo(() => personalMasters?.marital_status ?? [], [personalMasters]);
|
|
const religionOptions = useMemo(() => personalMasters?.religion ?? [], [personalMasters]);
|
|
const profileForOptions = useMemo(() => personalMasters?.profileCreatedFor ?? [], [personalMasters]);
|
|
const languageOptions = useMemo(() => personalMasters?.languages ?? [], [personalMasters]);
|
|
const heightOptions = useMemo(() => personalMasters?.heights ?? [], [personalMasters]);
|
|
const complexionOptions = useMemo(() => personalMasters?.complextions ?? [], [personalMasters]);
|
|
const physicalStatusOptions = useMemo(() => personalMasters?.physicalStatus ?? [], [personalMasters]);
|
|
|
|
const casteOptions = useMemo(() => {
|
|
const raw = casteQuery.data;
|
|
if (!raw) return [];
|
|
return Array.isArray(raw) ? raw : raw.caste || raw.data || [];
|
|
}, [casteQuery.data]);
|
|
|
|
const subCasteOptions = useMemo(() => {
|
|
const raw = subCasteQuery.data;
|
|
if (!raw) return [];
|
|
return Array.isArray(raw) ? raw : raw.sub_caste || raw.subCaste || raw.data || [];
|
|
}, [subCasteQuery.data]);
|
|
|
|
useEffect(() => {
|
|
if (!isEditMode) {
|
|
if (!data.religion) dispatch(updatePersonalDetails({ religion: 1 }));
|
|
if (!data.caste) dispatch(updatePersonalDetails({ caste: 1 }));
|
|
}
|
|
}, [isEditMode, data.religion, data.caste, dispatch]);
|
|
|
|
useEffect(() => {
|
|
if (nameInputRef.current) {
|
|
nameInputRef.current.focus();
|
|
}
|
|
}, []);
|
|
|
|
const handleChange = (field, value) => {
|
|
const updates = { [field]: value };
|
|
const fieldsToClear = [field];
|
|
|
|
if (field === "name" && !/^[a-zA-Z\s]*$/.test(value)) return;
|
|
|
|
if (field === "mobile") {
|
|
setMobileNumberError("");
|
|
if (data.verifiedMobileNumber && value === data.verifiedMobileNumber) {
|
|
setMobileOtpVerified(true);
|
|
setShowOtp(false);
|
|
} else {
|
|
setMobileOtpVerified(false);
|
|
setShowOtp(false);
|
|
setOtp(new Array(OTP_LENGTH).fill(""));
|
|
setOtpError("");
|
|
setOtpTimer(0);
|
|
}
|
|
}
|
|
|
|
if (field === "religion") {
|
|
updates.caste = "";
|
|
updates.sub_caste = "";
|
|
fieldsToClear.push("caste", "sub_caste");
|
|
}
|
|
if (field === "caste") {
|
|
updates.sub_caste = "";
|
|
fieldsToClear.push("sub_caste");
|
|
}
|
|
|
|
dispatch(updatePersonalDetails(updates));
|
|
if (onFieldChange) onFieldChange(fieldsToClear);
|
|
|
|
if (!isEditMode) {
|
|
dispatch(clearAllStepsFrom(2));
|
|
}
|
|
|
|
if (localErrors[field]) {
|
|
setLocalErrors(prev => ({ ...prev, [field]: "" }));
|
|
}
|
|
};
|
|
|
|
const handleMobileSubmit = async () => {
|
|
if (!data.mobile || data.mobile.length !== 10) {
|
|
setMobileNumberError("Please enter a valid 10-digit mobile number");
|
|
return;
|
|
}
|
|
try {
|
|
setMobileNumberError("");
|
|
const res = await sendOtp.mutateAsync(data.mobile);
|
|
toast.success(res?.message || "OTP sent successfully");
|
|
setShowOtp(true);
|
|
setOtpTimer(OTP_TIMER_SEC);
|
|
setOtp(new Array(OTP_LENGTH).fill(""));
|
|
setMobileOtpVerified(false);
|
|
} catch (error) {
|
|
const msg = error?.response?.data?.message || "Failed to send OTP";
|
|
setMobileNumberError(msg);
|
|
toast.error(msg);
|
|
}
|
|
};
|
|
|
|
const handleOtpChange = (index, value) => {
|
|
if (!/^\d*$/.test(value)) return;
|
|
const newOtp = [...otp];
|
|
newOtp[index] = value.slice(-1);
|
|
setOtp(newOtp);
|
|
if (value && index < OTP_LENGTH - 1) {
|
|
document.getElementById(`otp-${index + 1}`)?.focus();
|
|
}
|
|
};
|
|
|
|
const handleOtpSubmit = async () => {
|
|
if (otp.some(d => d === "")) {
|
|
setOtpError("Complete OTP is required");
|
|
return;
|
|
}
|
|
try {
|
|
const res = await verifyOtp.mutateAsync({
|
|
mobile: data.mobile,
|
|
otp: otp.join(""),
|
|
});
|
|
toast.success(res?.message || "OTP verified successfully");
|
|
setMobileOtpVerified(true);
|
|
dispatch(updatePersonalDetails({ verifiedMobileNumber: data.mobile }));
|
|
setOtpError("");
|
|
} catch (error) {
|
|
setOtpError("Invalid or expired OTP");
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (otpTimer <= 0) {
|
|
if (showOtp && !mobileOtpVerified) {
|
|
setOtpError("OTP expired, please resend");
|
|
setOtp(new Array(OTP_LENGTH).fill(""));
|
|
}
|
|
return;
|
|
}
|
|
const timerId = setInterval(() => setOtpTimer(t => t - 1), 1000);
|
|
return () => clearInterval(timerId);
|
|
}, [otpTimer, showOtp, mobileOtpVerified]);
|
|
|
|
const validateForm = () => {
|
|
const newErrors = {};
|
|
if (!data.profile_for) newErrors.profile_for = "Required";
|
|
if (!data.gender) newErrors.gender = "Required";
|
|
if (!data.name) newErrors.name = "Required";
|
|
if (!data.mobile) newErrors.mobile = "Required";
|
|
if (!data.email) newErrors.email = "Required";
|
|
if (!isEditMode && !data.password) newErrors.password = "Required";
|
|
if (!isEditMode && data.password !== data.confirmPassword) newErrors.confirmPassword = "Passwords do not match";
|
|
if (!data.marital_status) newErrors.marital_status = "Required";
|
|
if (!data.height) newErrors.height = "Required";
|
|
if (!data.complexion) newErrors.complexion = "Required";
|
|
if (!data.physical_status) newErrors.physical_status = "Required";
|
|
if (!data.mother_language) newErrors.mother_language = "Required";
|
|
if (!data.do_you_speak_telugu && data.do_you_speak_telugu !== 0) newErrors.do_you_speak_telugu = "Required";
|
|
if (!data.inter_caste_parents && data.inter_caste_parents !== 0) newErrors.inter_caste_parents = "Required";
|
|
if (data.known_languages.length === 0) newErrors.known_languages = "Required";
|
|
|
|
setLocalErrors(newErrors);
|
|
return Object.keys(newErrors).length === 0;
|
|
};
|
|
|
|
const scrollToError = (errorMap) => {
|
|
const errorFields = Object.keys(errorMap);
|
|
if (errorFields.length > 0) {
|
|
const fieldId = errorFields[0];
|
|
const element = document.getElementById(fieldId);
|
|
if (element) {
|
|
element.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
|
|
// Use a timeout to allow the smooth scroll to complete
|
|
setTimeout(() => {
|
|
// For MUI Select, the focusable element is often the div with role="combobox" or the button
|
|
const focusable = element.querySelector('[role="combobox"]') ||
|
|
element.querySelector('[role="button"]') ||
|
|
element.querySelector("input") ||
|
|
element.querySelector("select") ||
|
|
element;
|
|
if (focusable && typeof focusable.focus === "function") {
|
|
focusable.focus();
|
|
}
|
|
}, 500);
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleSubmit = async () => {
|
|
// 1. Prioritize OTP verification if showOtp is active
|
|
if (!mobileOtpVerified && !isEditMode && showOtp) {
|
|
setOtpError("Please enter and verify OTP");
|
|
const firstOtp = document.getElementById("otp-0");
|
|
if (firstOtp) {
|
|
firstOtp.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
setTimeout(() => {
|
|
const input = firstOtp.querySelector("input") || firstOtp;
|
|
if (input && typeof input.focus === "function") input.focus();
|
|
}, 500);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 2. Perform general form validation
|
|
if (!validateForm()) {
|
|
const newErrors = {};
|
|
if (!data.profile_for) newErrors.profile_for = "Required";
|
|
if (!data.gender) newErrors.gender = "Required";
|
|
if (!data.name) newErrors.name = "Required";
|
|
if (!data.mobile) newErrors.mobile = "Required";
|
|
if (!data.email) newErrors.email = "Required";
|
|
if (!isEditMode && !data.password) newErrors.password = "Required";
|
|
if (!isEditMode && data.password !== data.confirmPassword) newErrors.confirmPassword = "Passwords do not match";
|
|
if (!data.marital_status) newErrors.marital_status = "Required";
|
|
if (!data.height) newErrors.height = "Required";
|
|
if (!data.complexion) newErrors.complexion = "Required";
|
|
if (!data.physical_status) newErrors.physical_status = "Required";
|
|
if (!data.mother_language) newErrors.mother_language = "Required";
|
|
if (!data.do_you_speak_telugu && data.do_you_speak_telugu !== 0) newErrors.do_you_speak_telugu = "Required";
|
|
if (!data.inter_caste_parents && data.inter_caste_parents !== 0) newErrors.inter_caste_parents = "Required";
|
|
if (data.known_languages.length === 0) newErrors.known_languages = "Required";
|
|
|
|
toast.error("Please fill all mandatory fields");
|
|
scrollToError(newErrors);
|
|
return;
|
|
}
|
|
|
|
// 3. Final mobile verification check if showOtp was not yet active
|
|
if (!mobileOtpVerified && !isEditMode) {
|
|
setMobileNumberError("Please verify your mobile number");
|
|
const mobileField = document.getElementById("mobile");
|
|
if (mobileField) {
|
|
mobileField.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
setTimeout(() => {
|
|
const input = mobileField.querySelector("input") || mobileField;
|
|
if (input && typeof input.focus === "function") input.focus();
|
|
}, 500);
|
|
}
|
|
return;
|
|
}
|
|
|
|
onSubmitStep();
|
|
};
|
|
|
|
return (
|
|
<div className="w-full max-w-[1200px] mx-auto py-6 md:px-2 rounded-8">
|
|
<form noValidate autoComplete="off" style={{ padding: 16 }}>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-x-20 gap-y-10 mb-6">
|
|
{/* 1. Profile Created for */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Profile Created for{requiredMark}</label>
|
|
<FormControl fullWidth error={Boolean(errors.profile_for)} variant="outlined" id="profile_for">
|
|
<InputLabel>Select Profile Created For</InputLabel>
|
|
<Select
|
|
value={data.profile_for}
|
|
label="Select Profile Created For"
|
|
onChange={(e) => handleChange("profile_for", e.target.value)}
|
|
>
|
|
{profileForOptions.map((opt) => (
|
|
<MenuItem key={opt.id} value={opt.id}>{opt.profile_for_name}</MenuItem>
|
|
))}
|
|
</Select>
|
|
{errors.profile_for && <FormHelperText>{errors.profile_for}</FormHelperText>}
|
|
</FormControl>
|
|
</div>
|
|
|
|
{/* 2. Gender */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Gender{requiredMark}</label>
|
|
<FormControl fullWidth error={Boolean(errors.gender)} variant="outlined" id="gender">
|
|
<InputLabel>Select Gender</InputLabel>
|
|
<Select
|
|
value={data.gender}
|
|
label="Select Gender"
|
|
onChange={(e) => handleChange("gender", e.target.value)}
|
|
>
|
|
{genderOptions.map((opt) => (
|
|
<MenuItem key={opt} value={opt}>{opt}</MenuItem>
|
|
))}
|
|
</Select>
|
|
{errors.gender && <FormHelperText>{errors.gender}</FormHelperText>}
|
|
</FormControl>
|
|
</div>
|
|
|
|
{/* 3. Name */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Name{requiredMark}</label>
|
|
<TextField
|
|
id="name"
|
|
fullWidth
|
|
inputRef={nameInputRef}
|
|
label="Enter Name"
|
|
value={data.name}
|
|
onChange={(e) => handleChange("name", e.target.value)}
|
|
error={Boolean(errors.name)}
|
|
helperText={errors.name}
|
|
variant="outlined"
|
|
/>
|
|
</div>
|
|
|
|
{/* 4. Mobile Number */}
|
|
<div className="flex flex-col gap-2" id="mobile">
|
|
<label className="text-gray-900 text-[15px]">Mobile Number{requiredMark}</label>
|
|
<Box sx={{ display: "flex", gap: 2, flexDirection: { xs: "column", sm: "row" } }}>
|
|
<TextField
|
|
fullWidth
|
|
inputRef={mobileInputRef}
|
|
label="Enter Mobile Number"
|
|
value={data.mobile}
|
|
onChange={(e) => handleChange("mobile", e.target.value)}
|
|
error={Boolean(errors.mobile) || Boolean(mobileNumberError)}
|
|
helperText={mobileNumberError || errors.mobile}
|
|
inputProps={{ maxLength: 10 }}
|
|
disabled={isEditMode}
|
|
InputProps={{
|
|
endAdornment: mobileOtpVerified && (
|
|
<InputAdornment position="end">
|
|
<CheckCircleIcon color="success" />
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
/>
|
|
{!isEditMode && !mobileOtpVerified && !showOtp && (
|
|
<Button variant="contained" onClick={handleMobileSubmit} sx={{ height: 56, minWidth: 120 }}>
|
|
Send OTP
|
|
</Button>
|
|
)}
|
|
</Box>
|
|
|
|
{showOtp && !mobileOtpVerified && (
|
|
<Box sx={{ mt: 2, p: 2, border: "1px solid #ddd", borderRadius: 1 }}>
|
|
<Typography variant="subtitle2" sx={{ mb: 1 }}>Enter OTP</Typography>
|
|
<Box sx={{ display: "flex", gap: 1 }}>
|
|
{otp.map((digit, i) => (
|
|
<TextField
|
|
key={i}
|
|
id={`otp-${i}`}
|
|
value={digit}
|
|
onChange={(e) => handleOtpChange(i, e.target.value)}
|
|
inputProps={{ maxLength: 1, style: { textAlign: "center", width: 40 } }}
|
|
/>
|
|
))}
|
|
<Button variant="contained" onClick={handleOtpSubmit} disabled={otp.some(d => !d)}>
|
|
Verify
|
|
</Button>
|
|
</Box>
|
|
{otpError && <Typography variant="caption" color="error">{otpError}</Typography>}
|
|
<Typography variant="caption" display="block" sx={{ mt: 1 }}>
|
|
{otpTimer > 0 ? `Resend in ${otpTimer}s` : (
|
|
<Button size="small" onClick={handleMobileSubmit}>Resend OTP</Button>
|
|
)}
|
|
</Typography>
|
|
</Box>
|
|
)}
|
|
</div>
|
|
|
|
{/* 5. Email Id */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Email Id{requiredMark}</label>
|
|
<TextField
|
|
id="email"
|
|
fullWidth
|
|
label="Enter Email"
|
|
value={data.email}
|
|
onChange={(e) => handleChange("email", e.target.value)}
|
|
error={Boolean(errors.email)}
|
|
helperText={errors.email}
|
|
variant="outlined"
|
|
/>
|
|
</div>
|
|
|
|
{!isEditMode && (
|
|
<>
|
|
{/* 6. Create Password */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Create Password{requiredMark}</label>
|
|
<TextField
|
|
id="password"
|
|
fullWidth
|
|
type={showPassword ? "text" : "password"}
|
|
label="Enter Password"
|
|
value={data.password}
|
|
onChange={(e) => handleChange("password", e.target.value)}
|
|
error={Boolean(errors.password)}
|
|
helperText={errors.password}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
<IconButton onClick={() => setShowPassword(!showPassword)} edge="end">
|
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
|
</IconButton>
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{/* 7. Confirm Password */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Confirm Password{requiredMark}</label>
|
|
<TextField
|
|
id="confirmPassword"
|
|
fullWidth
|
|
type={showConfirmPassword ? "text" : "password"}
|
|
label="Confirm Password"
|
|
value={data.confirmPassword}
|
|
onChange={(e) => handleChange("confirmPassword", e.target.value)}
|
|
error={Boolean(errors.confirmPassword)}
|
|
helperText={errors.confirmPassword}
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
<IconButton onClick={() => setShowConfirmPassword(!showConfirmPassword)} edge="end">
|
|
{showConfirmPassword ? <VisibilityOff /> : <Visibility />}
|
|
</IconButton>
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{/* 8. Marital Status */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Marital Status{requiredMark}</label>
|
|
<FormControl fullWidth error={Boolean(errors.marital_status)} id="marital_status">
|
|
<InputLabel>Select Marital Status</InputLabel>
|
|
<Select
|
|
value={data.marital_status}
|
|
label="Select Marital Status"
|
|
onChange={(e) => handleChange("marital_status", e.target.value)}
|
|
>
|
|
{maritalStatusOptions.map((opt) => (
|
|
<MenuItem key={opt.id} value={opt.id}>{opt.marital_status_name}</MenuItem>
|
|
))}
|
|
</Select>
|
|
{errors.marital_status && <FormHelperText>{errors.marital_status}</FormHelperText>}
|
|
</FormControl>
|
|
</div>
|
|
|
|
{/* 9. Height */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Height{requiredMark}</label>
|
|
<FormControl fullWidth error={Boolean(errors.height)} id="height">
|
|
<InputLabel>Select Height</InputLabel>
|
|
<Select
|
|
value={data.height}
|
|
label="Select Height"
|
|
onChange={(e) => handleChange("height", e.target.value)}
|
|
>
|
|
{heightOptions.map((opt) => (
|
|
<MenuItem key={opt.id} value={opt.id}>{opt.height_text}</MenuItem>
|
|
))}
|
|
</Select>
|
|
{errors.height && <FormHelperText>{errors.height}</FormHelperText>}
|
|
</FormControl>
|
|
</div>
|
|
|
|
{/* 10. Weight */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Weight (kg)</label>
|
|
<TextField
|
|
id="weight"
|
|
fullWidth
|
|
type="number"
|
|
label="Enter Weight"
|
|
value={data.weight}
|
|
onChange={(e) => handleChange("weight", e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
{/* 11. Select Complexion */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Select Complexion{requiredMark}</label>
|
|
<FormControl fullWidth error={Boolean(errors.complexion)} id="complexion">
|
|
<InputLabel>Select Complexion</InputLabel>
|
|
<Select
|
|
value={data.complexion}
|
|
label="Select Complexion"
|
|
onChange={(e) => handleChange("complexion", e.target.value)}
|
|
>
|
|
{complexionOptions.map((opt) => (
|
|
<MenuItem key={opt.id} value={opt.id}>{opt.complexion_name}</MenuItem>
|
|
))}
|
|
</Select>
|
|
{errors.complexion && <FormHelperText>{errors.complexion}</FormHelperText>}
|
|
</FormControl>
|
|
</div>
|
|
|
|
{/* 12. Select Physical Status */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Select Physical Status{requiredMark}</label>
|
|
<FormControl fullWidth error={Boolean(errors.physical_status)} id="physical_status">
|
|
<InputLabel>Select Physical Status</InputLabel>
|
|
<Select
|
|
value={data.physical_status}
|
|
label="Select Physical Status"
|
|
onChange={(e) => handleChange("physical_status", e.target.value)}
|
|
>
|
|
{physicalStatusOptions.map((opt) => (
|
|
<MenuItem key={opt.id} value={opt.id}>{opt.physical_status_name}</MenuItem>
|
|
))}
|
|
</Select>
|
|
{errors.physical_status && <FormHelperText>{errors.physical_status}</FormHelperText>}
|
|
</FormControl>
|
|
</div>
|
|
|
|
{/* 13. Religion */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Religion</label>
|
|
<FormControl fullWidth>
|
|
<InputLabel>Select Religion</InputLabel>
|
|
<Select
|
|
value={data.religion}
|
|
label="Select Religion"
|
|
onChange={(e) => handleChange("religion", e.target.value)}
|
|
>
|
|
{religionOptions.map((opt) => (
|
|
<MenuItem key={opt.id} value={opt.id}>{opt.religion_name}</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
</div>
|
|
|
|
{/* 14. Caste / Community */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Caste / Community</label>
|
|
<FormControl fullWidth disabled={casteQuery.isLoading}>
|
|
<InputLabel>Select Caste</InputLabel>
|
|
<Select
|
|
value={data.caste}
|
|
label="Select Caste"
|
|
onChange={(e) => handleChange("caste", e.target.value)}
|
|
>
|
|
{casteOptions.map((opt) => (
|
|
<MenuItem key={opt.id} value={opt.id}>{opt.caste_name}</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
</div>
|
|
|
|
{/* 15. Sub-Sect */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Sub-Sect{requiredMark}</label>
|
|
<FormControl fullWidth disabled={!data.caste || subCasteQuery.isLoading} id="sub_caste">
|
|
<InputLabel>Select Sub-Sect</InputLabel>
|
|
<Select
|
|
value={data.sub_caste}
|
|
label="Select Sub-Sect"
|
|
onChange={(e) => handleChange("sub_caste", e.target.value)}
|
|
>
|
|
{subCasteOptions.map((opt) => (
|
|
<MenuItem key={opt.id} value={opt.id}>{opt.sub_caste_name}</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
</div>
|
|
|
|
{/* 16. Willing to marry from the same sub sect */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Willing to marry from the same sub sect</label>
|
|
<FormControl fullWidth>
|
|
<InputLabel>Select Option</InputLabel>
|
|
<Select
|
|
value={data.willing_to_marry}
|
|
label="Select Option"
|
|
onChange={(e) => handleChange("willing_to_marry", e.target.value)}
|
|
>
|
|
<MenuItem value="Yes">Yes</MenuItem>
|
|
<MenuItem value="No">No</MenuItem>
|
|
<MenuItem value="Any">Any</MenuItem>
|
|
</Select>
|
|
</FormControl>
|
|
</div>
|
|
|
|
{/* 17. Inter-Caste Parents */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Inter-Caste Parents{requiredMark}</label>
|
|
<FormControl fullWidth error={Boolean(errors.inter_caste_parents)} id="inter_caste_parents">
|
|
<InputLabel>Select Option</InputLabel>
|
|
<Select
|
|
value={data.inter_caste_parents}
|
|
label="Select Option"
|
|
onChange={(e) => handleChange("inter_caste_parents", e.target.value)}
|
|
>
|
|
<MenuItem value={1}>Yes</MenuItem>
|
|
<MenuItem value={0}>No</MenuItem>
|
|
</Select>
|
|
{errors.inter_caste_parents && <FormHelperText>{errors.inter_caste_parents}</FormHelperText>}
|
|
</FormControl>
|
|
</div>
|
|
|
|
{/* 18. Parents Details */}
|
|
<div className="flex flex-col gap-2 md:col-span-2">
|
|
<label className="text-gray-900 text-[15px]">Parents Details</label>
|
|
<TextField
|
|
id="inter_caste_parents_details"
|
|
fullWidth
|
|
multiline
|
|
rows={3}
|
|
label="Enter Details"
|
|
value={data.inter_caste_parents_details}
|
|
onChange={(e) => handleChange("inter_caste_parents_details", e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
{/* 19. Gothram (optional) */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Gothram (optional)</label>
|
|
<TextField
|
|
id="gothram"
|
|
fullWidth
|
|
label="Enter Gothram"
|
|
value={data.gothram}
|
|
onChange={(e) => handleChange("gothram", e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
{/* 20. Do you speak Telugu */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Do you speak Telugu?{requiredMark}</label>
|
|
<FormControl fullWidth error={Boolean(errors.do_you_speak_telugu)} id="do_you_speak_telugu">
|
|
<InputLabel>Select Option</InputLabel>
|
|
<Select
|
|
value={data.do_you_speak_telugu}
|
|
label="Select Option"
|
|
onChange={(e) => handleChange("do_you_speak_telugu", e.target.value)}
|
|
>
|
|
<MenuItem value={1}>Yes</MenuItem>
|
|
<MenuItem value={0}>No</MenuItem>
|
|
</Select>
|
|
{errors.do_you_speak_telugu && <FormHelperText>{errors.do_you_speak_telugu}</FormHelperText>}
|
|
</FormControl>
|
|
</div>
|
|
|
|
{/* 21. About you */}
|
|
<div className="flex flex-col gap-2 md:col-span-2">
|
|
<label className="text-gray-900 text-[15px]">About you</label>
|
|
<TextField
|
|
id="about_us"
|
|
fullWidth
|
|
multiline
|
|
rows={4}
|
|
label="Describe yourself"
|
|
value={data.about_us}
|
|
onChange={(e) => handleChange("about_us", e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
{/* 22. Select Languages Spoken */}
|
|
<div className="flex flex-col gap-2 md:col-span-2">
|
|
<label className="text-gray-900 text-[15px]">Select Languages Spoken{requiredMark}</label>
|
|
<Autocomplete
|
|
id="known_languages"
|
|
multiple
|
|
options={languageOptions}
|
|
getOptionLabel={(opt) => opt.language || ""}
|
|
value={languageOptions.filter(opt => data.known_languages.includes(opt.id))}
|
|
onChange={(_, newValue) => handleChange("known_languages", newValue.map(v => v.id))}
|
|
renderInput={(params) => (
|
|
<TextField
|
|
{...params}
|
|
label="Select Languages"
|
|
error={Boolean(errors.known_languages)}
|
|
helperText={errors.known_languages}
|
|
/>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
{/* 23. Select Mother Tongue */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Select Mother Tongue{requiredMark}</label>
|
|
<FormControl fullWidth error={Boolean(errors.mother_language)} id="mother_language">
|
|
<InputLabel>Select Mother Tongue</InputLabel>
|
|
<Select
|
|
value={data.mother_language}
|
|
label="Select Mother Tongue"
|
|
onChange={(e) => handleChange("mother_language", e.target.value)}
|
|
>
|
|
{languageOptions.map((opt) => (
|
|
<MenuItem key={opt.id} value={opt.id}>{opt.language}</MenuItem>
|
|
))}
|
|
</Select>
|
|
{errors.mother_language && <FormHelperText>{errors.mother_language}</FormHelperText>}
|
|
</FormControl>
|
|
</div>
|
|
|
|
{/* 24. Upload Profile */}
|
|
<div className="flex flex-col gap-2 md:col-span-2">
|
|
<label className="text-gray-900 text-[15px]">Upload Profile ( Max 3 image with 10 MB ){requiredMark}</label>
|
|
<AdvancedDropzone
|
|
id="profiles"
|
|
value={data.profiles || []}
|
|
onChange={(files) => handleChange("profiles", files)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex justify-center mt-10">
|
|
<Button
|
|
id="save-button"
|
|
variant="contained"
|
|
size="large"
|
|
onClick={handleSubmit}
|
|
sx={{ px: 8, py: 1.5, borderRadius: 2 }}
|
|
>
|
|
{isEditMode ? "Save Changes" : "Save & Continue"}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default PersonalDetailsForm;
|