549 lines
18 KiB
JavaScript
549 lines
18 KiB
JavaScript
import React, { useEffect, useMemo, useRef } from "react";
|
|
import { useDispatch, useSelector } from "react-redux";
|
|
import { updatePartnerPreferences } from "../redux/registrationFormSlice";
|
|
import {
|
|
Grid,
|
|
FormControl,
|
|
InputLabel,
|
|
Select,
|
|
MenuItem,
|
|
Button,
|
|
Checkbox,
|
|
ListItemText,
|
|
TextField,
|
|
} from "@mui/material";
|
|
import { usePartnerPreferenceMasters } from "../hooks/useMasters";
|
|
import { useCityMasters, useSubCasteMasters } from "../hooks/useDependentMasters";
|
|
|
|
const PartnerPreferencesForm = ({
|
|
onSubmitStep,
|
|
onSkipStep,
|
|
errors,
|
|
onFieldChange,
|
|
}) => {
|
|
const dispatch = useDispatch();
|
|
const data = useSelector((state) => state.registerform.partnerPreferences);
|
|
const inputRef = useRef(null);
|
|
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
|
|
|
|
const { data: masters, isLoading: isPartnerMastersLoading } =
|
|
usePartnerPreferenceMasters();
|
|
const subCasteQuery = useSubCasteMasters(data.castes);
|
|
const cityQuery = useCityMasters(data.states);
|
|
|
|
const ageOptions = useMemo(() => Array.from({ length: 53 }, (_, i) => i + 18), []);
|
|
|
|
const heightOptions = useMemo(() => {
|
|
const raw = masters;
|
|
if (!raw) return [];
|
|
return raw.heights || raw.height || [];
|
|
}, [masters]);
|
|
|
|
const maritalStatusOptions = useMemo(() => {
|
|
const raw = masters;
|
|
if (!raw) return [];
|
|
return raw.maritalStatus || raw.marital_status || [];
|
|
}, [masters]);
|
|
|
|
const starOptions = useMemo(() => {
|
|
const raw = masters;
|
|
if (!raw) return [];
|
|
return raw.stars || raw.star || [];
|
|
}, [masters]);
|
|
|
|
const employeeTypeOptions = useMemo(() => {
|
|
const raw = masters;
|
|
if (!raw) return [];
|
|
return raw.employeeTypes || raw.employee_type || [];
|
|
}, [masters]);
|
|
|
|
const casteOptions = useMemo(() => {
|
|
const raw = masters;
|
|
if (!raw) return [];
|
|
if (Array.isArray(raw)) return raw;
|
|
return raw.caste || raw.castes || [];
|
|
}, [masters]);
|
|
|
|
const occupationOptions = useMemo(() => {
|
|
const raw = masters;
|
|
if (!raw) return [];
|
|
if (Array.isArray(raw)) return raw;
|
|
return raw.occupation || raw.occupations || [];
|
|
}, [masters]);
|
|
|
|
const educationOptions = useMemo(() => {
|
|
const raw = masters;
|
|
if (!raw) return [];
|
|
if (Array.isArray(raw)) return raw;
|
|
return raw.education || raw.educations || [];
|
|
}, [masters]);
|
|
|
|
const hobbyOptions = useMemo(() => {
|
|
const raw = masters;
|
|
if (!raw) return [];
|
|
if (Array.isArray(raw)) return raw;
|
|
return raw.hobbies || raw.hobby || [];
|
|
}, [masters]);
|
|
|
|
const annualIncomeOptions = useMemo(() => {
|
|
const raw = masters;
|
|
if (!raw) return [];
|
|
if (Array.isArray(raw)) return raw;
|
|
return raw.annual_income || raw.annualIncome || [];
|
|
}, [masters]);
|
|
|
|
const currencyOptions = useMemo(() => ["INR", "USD"], []);
|
|
|
|
const stateOptions = useMemo(() => {
|
|
const raw = masters;
|
|
if (!raw) return [];
|
|
if (Array.isArray(raw)) return raw;
|
|
return raw.state || raw.states || [];
|
|
}, [masters]);
|
|
|
|
const subCasteOptions = useMemo(() => {
|
|
const raw = subCasteQuery.data;
|
|
if (!raw) return [];
|
|
if (Array.isArray(raw)) {
|
|
const merged = raw.flatMap((entry) => {
|
|
if (!entry) return [];
|
|
if (Array.isArray(entry)) return entry;
|
|
return entry.sub_caste || entry.subCaste || entry.data || [];
|
|
});
|
|
return merged;
|
|
}
|
|
return raw.sub_caste || raw.subCaste || raw.data || [];
|
|
}, [subCasteQuery.data]);
|
|
|
|
const cityOptions = useMemo(() => {
|
|
const raw = cityQuery.data;
|
|
if (!raw) return [];
|
|
if (Array.isArray(raw)) {
|
|
const merged = raw.flatMap((entry) => {
|
|
if (!entry) return [];
|
|
if (Array.isArray(entry)) return entry;
|
|
return entry.district || entry.districts || entry.data || [];
|
|
});
|
|
return merged;
|
|
}
|
|
return raw.district || raw.districts || raw.data || [];
|
|
}, [cityQuery.data]);
|
|
|
|
useEffect(() => {
|
|
inputRef.current?.focus();
|
|
}, []);
|
|
|
|
const handleChange = (field, value) => {
|
|
const arrayFields = new Set([
|
|
"castes",
|
|
"sub_castes",
|
|
"occupations",
|
|
"educations",
|
|
"states",
|
|
"districts",
|
|
"marital_statuses",
|
|
"birth_stars",
|
|
"employee_types",
|
|
"currencies",
|
|
]);
|
|
const nextValue =
|
|
arrayFields.has(field) && typeof value === "string"
|
|
? value.split(",").filter(Boolean)
|
|
: value;
|
|
const updates = { [field]: nextValue };
|
|
const fieldsToClear = [field];
|
|
|
|
if (field === "castes") {
|
|
updates.sub_castes = [];
|
|
fieldsToClear.push("sub_castes");
|
|
}
|
|
if (field === "states") {
|
|
updates.districts = [];
|
|
fieldsToClear.push("districts");
|
|
}
|
|
|
|
dispatch(updatePartnerPreferences(updates));
|
|
if (onFieldChange) onFieldChange(fieldsToClear);
|
|
};
|
|
|
|
const handleSubmit = () => {
|
|
console.log("Submitting partner preferences:", data);
|
|
onSubmitStep();
|
|
};
|
|
|
|
const renderMultiSelect = ({
|
|
name,
|
|
label,
|
|
options,
|
|
value,
|
|
getLabel,
|
|
getValue,
|
|
disabled,
|
|
}) => (
|
|
<FormControl fullWidth variant="outlined" error={Boolean(errors[name])}>
|
|
<InputLabel id={`${name}-label`}>Select {label}</InputLabel>
|
|
<Select
|
|
labelId={`${name}-label`}
|
|
label={`Select ${label}`}
|
|
name={name}
|
|
multiple
|
|
value={value}
|
|
onChange={(e) => handleChange(name, e.target.value)}
|
|
disabled={disabled}
|
|
renderValue={(selected) =>
|
|
selected
|
|
.map((id) => {
|
|
const item = options.find((opt) => getValue(opt) === id);
|
|
return item ? getLabel(item) : id;
|
|
})
|
|
.join(", ")
|
|
}
|
|
sx={{
|
|
"& .MuiSelect-select.Mui-disabled": {
|
|
cursor: "not-allowed",
|
|
},
|
|
}}
|
|
>
|
|
{options.map((opt) => {
|
|
const optValue = getValue(opt);
|
|
const optLabel = getLabel(opt);
|
|
return (
|
|
<MenuItem key={optValue} value={optValue}>
|
|
<Checkbox checked={value.indexOf(optValue) > -1} />
|
|
<ListItemText primary={optLabel} />
|
|
</MenuItem>
|
|
);
|
|
})}
|
|
</Select>
|
|
{errors[name] && (
|
|
<p
|
|
style={{
|
|
color: "#d32f2f",
|
|
margin: "3px 14px 0 14px",
|
|
fontSize: "0.75rem",
|
|
}}
|
|
>
|
|
{errors[name]}
|
|
</p>
|
|
)}
|
|
</FormControl>
|
|
);
|
|
|
|
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. Age Range */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Age Range (From - To)</label>
|
|
<div className="flex gap-2">
|
|
<FormControl fullWidth variant="outlined" error={Boolean(errors.age_from)}>
|
|
<InputLabel>From Age</InputLabel>
|
|
<Select
|
|
id="age_from"
|
|
name="age_from"
|
|
value={data.age_from}
|
|
label="From Age"
|
|
inputRef={inputRef}
|
|
onChange={(e) => handleChange("age_from", e.target.value)}
|
|
>
|
|
{ageOptions.map((age) => (
|
|
<MenuItem key={age} value={age}>{age}</MenuItem>
|
|
))}
|
|
</Select>
|
|
{errors.age_from && (
|
|
<p style={{ color: "#d32f2f", margin: "3px 14px 0 14px", fontSize: "0.75rem" }}>
|
|
{errors.age_from}
|
|
</p>
|
|
)}
|
|
</FormControl>
|
|
<FormControl fullWidth variant="outlined" error={Boolean(errors.age_to)}>
|
|
<InputLabel>To Age</InputLabel>
|
|
<Select
|
|
id="age_to"
|
|
name="age_to"
|
|
value={data.age_to}
|
|
label="To Age"
|
|
onChange={(e) => handleChange("age_to", e.target.value)}
|
|
>
|
|
{ageOptions.map((age) => (
|
|
<MenuItem key={age} value={age}>{age}</MenuItem>
|
|
))}
|
|
</Select>
|
|
{errors.age_to && (
|
|
<p style={{ color: "#d32f2f", margin: "3px 14px 0 14px", fontSize: "0.75rem" }}>
|
|
{errors.age_to}
|
|
</p>
|
|
)}
|
|
</FormControl>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 2. Height Range */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Height Range (From - To)</label>
|
|
<div className="flex gap-2">
|
|
<FormControl fullWidth variant="outlined" error={Boolean(errors.height_from)}>
|
|
<InputLabel>From Height</InputLabel>
|
|
<Select
|
|
id="height_from"
|
|
name="height_from"
|
|
value={data.height_from}
|
|
label="From Height"
|
|
onChange={(e) => handleChange("height_from", e.target.value)}
|
|
>
|
|
{heightOptions.map((h) => (
|
|
<MenuItem key={h.id} value={h.id}>{h.height_text}</MenuItem>
|
|
))}
|
|
</Select>
|
|
{errors.height_from && (
|
|
<p style={{ color: "#d32f2f", margin: "3px 14px 0 14px", fontSize: "0.75rem" }}>
|
|
{errors.height_from}
|
|
</p>
|
|
)}
|
|
</FormControl>
|
|
<FormControl fullWidth variant="outlined" error={Boolean(errors.height_to)}>
|
|
<InputLabel>To Height</InputLabel>
|
|
<Select
|
|
id="height_to"
|
|
name="height_to"
|
|
value={data.height_to}
|
|
label="To Height"
|
|
onChange={(e) => handleChange("height_to", e.target.value)}
|
|
>
|
|
{heightOptions.map((h) => (
|
|
<MenuItem key={h.id} value={h.id}>{h.height_text}</MenuItem>
|
|
))}
|
|
</Select>
|
|
{errors.height_to && (
|
|
<p style={{ color: "#d32f2f", margin: "3px 14px 0 14px", fontSize: "0.75rem" }}>
|
|
{errors.height_to}
|
|
</p>
|
|
)}
|
|
</FormControl>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 3. Marital Status */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Marital Status</label>
|
|
{renderMultiSelect({
|
|
name: "marital_statuses",
|
|
label: "Marital Status",
|
|
options: maritalStatusOptions,
|
|
value: data.marital_statuses,
|
|
getLabel: (opt) => opt.marital_status_name || opt.name,
|
|
getValue: (opt) => opt.id,
|
|
disabled: isPartnerMastersLoading,
|
|
})}
|
|
</div>
|
|
|
|
{/* 4. Birth Star */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Birth Star</label>
|
|
{renderMultiSelect({
|
|
name: "birth_stars",
|
|
label: "Birth Star",
|
|
options: starOptions,
|
|
value: data.birth_stars,
|
|
getLabel: (opt) => opt.star_name || opt.name,
|
|
getValue: (opt) => opt.id,
|
|
disabled: isPartnerMastersLoading,
|
|
})}
|
|
</div>
|
|
|
|
{/* 5. Caste */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Caste</label>
|
|
{renderMultiSelect({
|
|
name: "castes",
|
|
label: "Caste",
|
|
options: casteOptions,
|
|
value: data.castes,
|
|
getLabel: (opt) => opt.caste_name || opt.name,
|
|
getValue: (opt) => opt.id,
|
|
disabled: isPartnerMastersLoading,
|
|
})}
|
|
</div>
|
|
|
|
{/* 6. Sub-Sect */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Sub-Sect</label>
|
|
{renderMultiSelect({
|
|
name: "sub_castes",
|
|
label: "Sub-Sect",
|
|
options: subCasteOptions,
|
|
value: data.sub_castes,
|
|
getLabel: (opt) => opt.sub_caste_name || opt.name,
|
|
getValue: (opt) => opt.id,
|
|
disabled: data.castes.length === 0 || subCasteQuery.isLoading,
|
|
})}
|
|
</div>
|
|
|
|
{/* 7. Qualification */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Qualification</label>
|
|
{renderMultiSelect({
|
|
name: "educations",
|
|
label: "Qualification",
|
|
options: educationOptions,
|
|
value: data.educations,
|
|
getLabel: (opt) => opt.education_name || opt.name,
|
|
getValue: (opt) => opt.id,
|
|
disabled: isPartnerMastersLoading,
|
|
})}
|
|
</div>
|
|
|
|
{/* 8. Occupation */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Occupation</label>
|
|
{renderMultiSelect({
|
|
name: "occupations",
|
|
label: "Occupation",
|
|
options: occupationOptions,
|
|
value: data.occupations,
|
|
getLabel: (opt) => opt.occupation_name || opt.name,
|
|
getValue: (opt) => opt.id,
|
|
disabled: isPartnerMastersLoading,
|
|
})}
|
|
</div>
|
|
|
|
{/* 9. Employee Type */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Employee Type</label>
|
|
{renderMultiSelect({
|
|
name: "employee_types",
|
|
label: "Employee Type",
|
|
options: employeeTypeOptions,
|
|
value: data.employee_types,
|
|
getLabel: (opt) => opt.employee_type_name || opt.name,
|
|
getValue: (opt) => opt.id,
|
|
disabled: isPartnerMastersLoading,
|
|
})}
|
|
</div>
|
|
|
|
{/* 10. Currency Type */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Income Currency Type</label>
|
|
{renderMultiSelect({
|
|
name: "currencies",
|
|
label: "Currency",
|
|
options: currencyOptions,
|
|
value: data.currencies,
|
|
getLabel: (opt) => opt,
|
|
getValue: (opt) => opt,
|
|
disabled: isPartnerMastersLoading,
|
|
})}
|
|
</div>
|
|
|
|
{/* 11. Income Range */}
|
|
{data.currencies.includes("INR") && (
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Annual Income (INR Range)</label>
|
|
<div className="flex gap-2">
|
|
<TextField
|
|
id="inr_from"
|
|
name="inr_from"
|
|
fullWidth
|
|
label="From (INR)"
|
|
value={data.inr_from}
|
|
error={Boolean(errors.inr_from)}
|
|
helperText={errors.inr_from}
|
|
onChange={(e) => handleChange("inr_from", e.target.value)}
|
|
/>
|
|
<TextField
|
|
id="inr_to"
|
|
name="inr_to"
|
|
fullWidth
|
|
label="To (INR)"
|
|
value={data.inr_to}
|
|
error={Boolean(errors.inr_to)}
|
|
helperText={errors.inr_to}
|
|
onChange={(e) => handleChange("inr_to", e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{data.currencies.includes("USD") && (
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">Annual Income (USD Range)</label>
|
|
<div className="flex gap-2">
|
|
<TextField
|
|
id="usd_from"
|
|
name="usd_from"
|
|
fullWidth
|
|
label="From (USD)"
|
|
value={data.usd_from}
|
|
error={Boolean(errors.usd_from)}
|
|
helperText={errors.usd_from}
|
|
onChange={(e) => handleChange("usd_from", e.target.value)}
|
|
/>
|
|
<TextField
|
|
id="usd_to"
|
|
name="usd_to"
|
|
fullWidth
|
|
label="To (USD)"
|
|
value={data.usd_to}
|
|
error={Boolean(errors.usd_to)}
|
|
helperText={errors.usd_to}
|
|
onChange={(e) => handleChange("usd_to", e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* 12. State */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">State</label>
|
|
{renderMultiSelect({
|
|
name: "states",
|
|
label: "State",
|
|
options: stateOptions,
|
|
value: data.states,
|
|
getLabel: (opt) => opt.state_name || opt.name,
|
|
getValue: (opt) => opt.id,
|
|
disabled: isPartnerMastersLoading,
|
|
})}
|
|
</div>
|
|
|
|
{/* 13. City */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-gray-900 text-[15px]">City</label>
|
|
{renderMultiSelect({
|
|
name: "districts",
|
|
label: "City",
|
|
options: cityOptions,
|
|
value: data.districts,
|
|
getLabel: (opt) => opt.district_name || opt.city_name || opt.name,
|
|
getValue: (opt) => opt.id,
|
|
disabled: data.states.length === 0 || cityQuery.isLoading,
|
|
})}
|
|
</div>
|
|
</div>
|
|
|
|
<Grid
|
|
size={12}
|
|
style={{
|
|
marginTop: 40,
|
|
display: "flex",
|
|
gap: 16,
|
|
justifyContent: "center",
|
|
}}
|
|
>
|
|
{onSkipStep && (
|
|
<Button variant="outlined" onClick={onSkipStep}>
|
|
Skip
|
|
</Button>
|
|
)}
|
|
<Button variant="contained" color="primary" onClick={handleSubmit}>
|
|
{onSkipStep ? "Next" : "Update"}
|
|
</Button>
|
|
</Grid>
|
|
</form>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default PartnerPreferencesForm;
|