thirukalyanamweb/src/feature/LifestyleDetailsForm.jsx

509 lines
17 KiB
JavaScript

import React, { useEffect, useMemo, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { updateLifestyleDetails } from "../redux/registrationFormSlice";
import {
Grid,
FormControl,
InputLabel,
Select,
MenuItem,
Button,
TextField,
Checkbox,
ListItemText,
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
Tooltip,
} from "@mui/material";
import { LocalizationProvider } from "@mui/x-date-pickers";
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
import { TimePicker } from "@mui/x-date-pickers/TimePicker";
import { AdapterDateFns } from "@mui/x-date-pickers/AdapterDateFns";
import { useLifestyleMasters } from "../hooks/useMasters";
import horoscopeImg from "../assets/images/horoscopeimg.png";
const LifestyleDetailsForm = ({
onSubmitStep,
onSkipStep,
errors,
onFieldChange,
}) => {
const dispatch = useDispatch();
const data = useSelector((state) => state.registerform.lifestyleDetails);
const inputRef = useRef(null);
const requiredMark = <span style={{ color: "#d32f2f" }}> *</span>;
const { data: lifestyleMasters, isLoading: isLifestyleMastersLoading } =
useLifestyleMasters();
const dietOptions = useMemo(() => {
const raw = lifestyleMasters;
if (!raw) return [];
if (Array.isArray(raw)) return raw;
return raw.diet || raw.diets || [];
}, [lifestyleMasters]);
const hobbyOptions = useMemo(() => {
const raw = lifestyleMasters;
if (!raw) return [];
if (Array.isArray(raw)) return raw;
return raw.hobbies || raw.hobby || [];
}, [lifestyleMasters]);
const grahaOptions = useMemo(() => {
const raw = lifestyleMasters;
if (!raw) return [];
if (Array.isArray(raw)) return raw;
return raw.grahas || raw.graha || [];
}, [lifestyleMasters]);
const [confirmDialog, setConfirmDialog] = useState({
open: false,
type: "",
house: null,
item: "",
sourceHouse: null,
pendingValue: [],
});
useEffect(() => {
inputRef.current?.focus();
}, []);
const handleChange = (field, value) => {
dispatch(updateLifestyleDetails({ [field]: value }));
if (onFieldChange) onFieldChange(field);
};
const handleMultiChange = (field, value) => {
const nextValue = Array.isArray(value) ? value : String(value).split(",");
dispatch(updateLifestyleDetails({ [field]: nextValue }));
if (onFieldChange) onFieldChange(field);
};
const handleChartChange = (type, house, newValue) => {
const currentChart = type === "graha" ? data.graha : data.amsam;
const currentHouseValue = currentChart?.[house] || [];
// Find added items
const addedItems = newValue.filter((item) => !currentHouseValue.includes(item));
if (addedItems.length > 0) {
for (const addedItem of addedItems) {
for (const [otherHouse, items] of Object.entries(currentChart || {})) {
if (String(otherHouse) !== String(house) && items && items.includes(addedItem)) {
setConfirmDialog({
open: true,
type,
house,
item: addedItem,
sourceHouse: otherHouse,
pendingValue: newValue,
});
return;
}
}
}
}
// No duplicates, proceed with update
const updates = {};
const nextChart = { ...(currentChart || {}) };
nextChart[house] = newValue;
if (type === "graha") {
updates.graha = nextChart;
} else {
updates.amsam = nextChart;
}
dispatch(updateLifestyleDetails(updates));
if (onFieldChange) onFieldChange(type);
};
const handleConfirmMove = () => {
const { type, house, item, sourceHouse, pendingValue } = confirmDialog;
const currentChart = type === "graha" ? data.graha : data.amsam;
const nextChart = { ...(currentChart || {}) };
// Remove from source house
if (nextChart[sourceHouse]) {
nextChart[sourceHouse] = nextChart[sourceHouse].filter((i) => i !== item);
}
// Add to target house
nextChart[house] = pendingValue;
const updates = type === "graha" ? { graha: nextChart } : { amsam: nextChart };
dispatch(updateLifestyleDetails(updates));
if (onFieldChange) onFieldChange(type);
setConfirmDialog({ ...confirmDialog, open: false });
};
const handleCancelMove = () => {
setConfirmDialog({ ...confirmDialog, open: false });
};
const handleSubmit = () => {
console.log("Submitting lifestyle details:", data);
onSubmitStep();
};
const parseDateValue = (value) => {
if (!value) return null;
const date = new Date(value);
return Number.isNaN(date.getTime()) ? null : date;
};
const parseTimeValue = (value) => {
if (!value || typeof value !== "string") return null;
const [hours, minutes] = value.split(":").map(Number);
if (Number.isNaN(hours) || Number.isNaN(minutes)) return null;
const date = new Date();
date.setHours(hours, minutes, 0, 0);
return date;
};
const formatDate = (value) => {
if (!(value instanceof Date) || Number.isNaN(value)) return "";
const y = value.getFullYear();
const m = String(value.getMonth() + 1).padStart(2, "0");
const d = String(value.getDate()).padStart(2, "0");
return `${y}-${m}-${d}`;
};
const formatTime = (value) => {
if (!(value instanceof Date) || Number.isNaN(value)) return "";
const h = String(value.getHours()).padStart(2, "0");
const m = String(value.getMinutes()).padStart(2, "0");
return `${h}:${m}`;
};
const renderChartCell = (label, value, onChange) => (
<Tooltip title={value && value.length > 0 ? value.join(", ") : ""} arrow placement="top">
<div className="bg-white border border-gray-300 rounded-lg p-2 flex flex-col items-center justify-center min-h-[70px]">
<span className="text-[10px] font-medium text-gray-700 text-center">
{label}
</span>
<FormControl fullWidth variant="standard" sx={{ mt: 0.5 }}>
<Select
multiple
displayEmpty
value={value}
onChange={(e) => onChange(e.target.value)}
renderValue={(selected) => {
if (!selected || selected.length === 0) return `+ Add ${label}`;
return (
<div style={{
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
maxWidth: "60px",
fontSize: "11px"
}}>
{selected.join(", ")}
</div>
);
}}
MenuProps={{
PaperProps: { style: { maxHeight: 280 } },
}}
>
{grahaOptions.map((opt) => (
<MenuItem key={opt} value={opt}>
<Checkbox checked={value.indexOf(opt) > -1} />
<ListItemText primary={opt} />
</MenuItem>
))}
</Select>
</FormControl>
</div>
</Tooltip>
);
const renderChartGrid = (type) => {
const getValue = (house) =>
(type === "graha" ? data.graha : data.amsam)?.[house] || [];
const onChange = (house, value) =>
handleChartChange(type, house, value);
const label = type === "graha" ? "Rasi" : "Navamsam";
return (
<div className="grid grid-cols-4 gap-2">
{renderChartCell(label, getValue(1), (value) => onChange(1, value))}
{renderChartCell(label, getValue(2), (value) => onChange(2, value))}
{renderChartCell(label, getValue(3), (value) => onChange(3, value))}
{renderChartCell(label, getValue(4), (value) => onChange(4, value))}
{renderChartCell(label, getValue(5), (value) => onChange(5, value))}
<div className="col-span-2 row-span-2 flex items-center justify-center">
<div className="w-full aspect-square rounded-full overflow-hidden bg-white">
<img src={horoscopeImg} alt="Horoscope" className="w-full h-full object-contain" />
</div>
</div>
{renderChartCell(label, getValue(6), (value) => onChange(6, value))}
{renderChartCell(label, getValue(7), (value) => onChange(7, value))}
{renderChartCell(label, getValue(8), (value) => onChange(8, value))}
{renderChartCell(label, getValue(9), (value) => onChange(9, value))}
{renderChartCell(label, getValue(10), (value) => onChange(10, value))}
{renderChartCell(label, getValue(11), (value) => onChange(11, value))}
{renderChartCell(label, getValue(12), (value) => onChange(12, value))}
</div>
);
};
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">
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">
Diet{requiredMark}
</label>
<FormControl fullWidth variant="outlined" error={Boolean(errors.diets)}>
<InputLabel id="diets-label">Select Diet</InputLabel>
<Select
labelId="diets-label"
label="Select Diet"
name="diets"
value={data.diets}
onChange={(e) => handleChange("diets", e.target.value)}
inputRef={inputRef}
disabled={isLifestyleMastersLoading}
sx={{
"& .MuiSelect-select.Mui-disabled": {
cursor: "not-allowed",
},
}}
>
{dietOptions.map((opt) => {
const value = opt.id ?? opt;
const label = opt.diet_name || opt.name || String(opt);
return (
<MenuItem key={value} value={value}>
{label}
</MenuItem>
);
})}
</Select>
{errors.diets && (
<p
style={{
color: "#d32f2f",
margin: "3px 14px 0 14px",
fontSize: "0.75rem",
}}
>
{errors.diets}
</p>
)}
</FormControl>
</div>
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">
Hobbies & Interests (Multi-select){requiredMark}
</label>
<FormControl
fullWidth
variant="outlined"
error={Boolean(errors.hobbies)}
>
<InputLabel id="hobbies-label">
Select Hobbies & Interests
</InputLabel>
<Select
labelId="hobbies-label"
label="Select Hobbies & Interests"
name="hobbies"
multiple
value={data.hobbies}
onChange={(e) => handleMultiChange("hobbies", e.target.value)}
disabled={isLifestyleMastersLoading}
renderValue={(selected) =>
selected
.map((id) => {
const item = hobbyOptions.find(
(opt) => (opt.id ?? opt) === id
);
return item?.hobby_name || item?.name || id;
})
.join(", ")
}
sx={{
"& .MuiSelect-select.Mui-disabled": {
cursor: "not-allowed",
},
}}
>
{hobbyOptions.map((opt) => {
const value = opt.id ?? opt;
const label = opt.hobby_name || opt.name || String(opt);
return (
<MenuItem key={value} value={value}>
<Checkbox checked={data.hobbies.indexOf(value) > -1} />
<ListItemText primary={label} />
</MenuItem>
);
})}
</Select>
{errors.hobbies && (
<p
style={{
color: "#d32f2f",
margin: "3px 14px 0 14px",
fontSize: "0.75rem",
}}
>
{errors.hobbies}
</p>
)}
</FormControl>
</div>
</div>
<div className="text-center py-4">
<h2 className="text-[18px] font-semibold text-gray-800">
Astrology / Horoscope
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 w-full max-w-[900px] mx-auto">
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">
Date of Birth{requiredMark}
</label>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DatePicker
format="dd/MM/yyyy"
disableFuture
value={parseDateValue(data.dob)}
onChange={(value) => handleChange("dob", formatDate(value))}
slotProps={{
textField: {
name: "dob",
fullWidth: true,
error: Boolean(errors.dob),
helperText: errors.dob,
},
}}
/>
</LocalizationProvider>
</div>
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">
Time of Birth{requiredMark}
</label>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<TimePicker
value={parseTimeValue(data.tob)}
onChange={(value) => handleChange("tob", formatTime(value))}
slotProps={{
textField: {
name: "tob",
fullWidth: true,
error: Boolean(errors.tob),
helperText: errors.tob,
},
}}
/>
</LocalizationProvider>
</div>
<div className="flex flex-col gap-2">
<label className="text-gray-900 text-[15px]">Place of Birth</label>
<TextField
fullWidth
name="placeOfBirth"
value={data.placeOfBirth}
onChange={(e) => handleChange("placeOfBirth", e.target.value)}
error={Boolean(errors.placeOfBirth)}
helperText={errors.placeOfBirth}
placeholder="Enter Place of Birth"
variant="outlined"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 w-full max-w-[950px] mx-auto mt-6">
<div>
<div className="py-4">
<h3 className="text-base font-semibold text-gray-800 mb-3">
Add Rasi
</h3>
{renderChartGrid("graha")}
{errors.graha_duplicate && (
<p style={{ color: "#d32f2f", fontSize: "0.75rem", marginTop: "4px" }}>
{errors.graha_duplicate}
</p>
)}
</div>
</div>
<div>
<div className="py-4">
<h3 className="text-base font-semibold text-gray-800 mb-3">
Add Navamsam
</h3>
{renderChartGrid("amsam")}
{errors.amsam_duplicate && (
<p style={{ color: "#d32f2f", fontSize: "0.75rem", marginTop: "4px" }}>
{errors.amsam_duplicate}
</p>
)}
</div>
</div>
</div>
<Grid
item
xs={12}
style={{
marginTop: 50,
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>
<Dialog
open={confirmDialog.open}
onClose={handleCancelMove}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">{"Duplicate Selection"}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
{confirmDialog.item} is already selected in House {confirmDialog.sourceHouse}. Do you want to move it to House {confirmDialog.house}?
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleCancelMove}>Cancel</Button>
<Button onClick={handleConfirmMove} autoFocus>Move</Button>
</DialogActions>
</Dialog>
</form>
</div>
);
};
export default LifestyleDetailsForm;