Android_Retail/src/Pages/Membership/MembershipForm.jsx

819 lines
28 KiB
React
Raw Normal View History

2026-01-27 18:27:29 +05:30
import { useCallback, useEffect, useState } from 'react';
import { Input, InputNumber, Select, Radio, Button, message } from 'antd';
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
import './MembershipForm.scss';
import { useDispatch } from 'react-redux';
import { getSession } from '../../Services/Others';
import { changeBreadCrumb } from '../../Features/AppPage/CenterPage.js';
import { Messages } from '../../Components/Notifications/Messages.jsx';
import {
postMembership,
putMembership,
} from '../../Features/Membership/Membership.js';
import { getAdmin } from '../../Features/ProductPage/ProductPage.js';
import { useLocation, useNavigate } from 'react-router-dom';
const subDirectory = import.meta.env.ENV_BASE_URL;
const { Option } = Select;
const DAYS_OF_WEEK = [
{ label: 'Monday', value: 'Monday' },
{ label: 'Tuesday', value: 'Tuesday' },
{ label: 'Wednesday', value: 'Wednesday' },
{ label: 'Thursday', value: 'Thursday' },
{ label: 'Friday', value: 'Friday' },
{ label: 'Saturday', value: 'Saturday' },
{ label: 'Sunday', value: 'Sunday' },
];
const MembershipForm = ({ formType = 'add' }) => {
const dispatch = useDispatch();
const navigate = useNavigate();
const location = useLocation();
const state = location?.state;
const editstate = state?.editstate;
console.log(editstate, 'editstate');
const AppId = getSession('AppId');
const CompId = getSession('CompId');
const BranchId = getSession('BranchId');
const UserId = getSession('UserId');
const [messageType, setMessageType] = useState(null);
const [messageData, setMessageData] = useState(null);
const [taxData, setTaxData] = useState([]);
const [membershipName, setMembershipName] = useState('');
const [nameError, setNameError] = useState('');
const [schedules, setSchedules] = useState([
{
id: Date.now(),
days: null,
price: null,
displayPrice: null,
taxId: null,
hrs: null,
discountType: 'F',
discountValue: null,
priority: 'N',
selectedDays: [],
remarks: '',
membershipId: null,
uniqueId: null,
planId: null,
activeStatus: null,
errors: {},
},
]);
const items = [
{
name: 'Home',
link: `${subDirectory}app-page/home`,
},
{
name: 'Membership List',
link: `${subDirectory}setting/membership-master/list`,
},
{
name: editstate ? 'Edit' : 'New',
link: null,
},
];
console.log(taxData, 'taxData');
useEffect(() => {
getTaxData();
dispatch(changeBreadCrumb({ items: items }));
if (editstate) {
// Populate membership name
setMembershipName(editstate?.MembershipName || '');
// Populate schedules from MembershipPlanDtl
if (
editstate?.MembershipPlanDtl &&
editstate.MembershipPlanDtl.length > 0
) {
const populatedSchedules = editstate.MembershipPlanDtl.map((plan) => ({
id: plan.UniqueId || Date.now(),
days: plan.PlanDuration,
price: plan.Price,
displayPrice: plan.DisplayPrice,
taxId: plan.TaxId,
hrs: plan.FreeSessionHrs,
discountType: plan.DiscountType,
discountValue: plan.Discount,
priority: plan.Priority,
selectedDays:
plan.Prioritydays?.[0]?.split(',')?.map((day) => day.trim()) || [],
remarks: plan.Remarks || '',
membershipId: plan.MembershipId,
uniqueId: plan.UniqueId,
planId: plan.PlanId,
activeStatus: plan.ActiveStatus,
errors: {},
}));
setSchedules(populatedSchedules);
}
}
}, []);
const getTaxData = async () => {
const res = await dispatch(
getAdmin({ CompId: CompId, AppId: AppId })
).unwrap();
if (res?.data?.statusCode === 1) {
setTaxData(res?.data?.data);
} else {
setTaxData([]);
}
};
const validateMembershipName = (value) => {
if (!value || value.trim() === '') {
setNameError('Membership name is required');
return false;
}
setNameError('');
return true;
};
const handleScheduleChange = (id, field, value) => {
const index = schedules.findIndex((schedule) => schedule.id === id);
const newSchedules = [...schedules];
newSchedules[index] = { ...newSchedules[index], [field]: value };
// ✅ Clear hrs when days change
if (field === 'days') {
newSchedules[index].hrs = null;
}
// ✅ Reset selectedDays if priority is N
if (field === 'priority' && value === 'N') {
newSchedules[index].selectedDays = [];
}
// Only validate the current field being changed
const currentErrors = { ...newSchedules[index].errors };
const fieldValidation = validateSingleField(
newSchedules[index],
field,
newSchedules
);
if (fieldValidation) {
currentErrors[field] = fieldValidation;
} else {
delete currentErrors[field];
}
newSchedules[index].errors = currentErrors;
setSchedules(newSchedules);
};
const validateSingleField = (schedule, field, allSchedules = schedules) => {
switch (field) {
case 'days':
if (!schedule.days) return 'Days is required';
break;
case 'price':
if (!schedule.price) return 'Price is required';
break;
case 'displayPrice':
if (!schedule.displayPrice) return 'Display price is required';
if (
schedule.displayPrice &&
schedule.price &&
Number(schedule.displayPrice) > Number(schedule.price)
) {
return 'Display price cannot be greater than price';
}
break;
case 'taxId':
if (!schedule.taxId) return 'Tax is required';
break;
case 'hrs':
if (!schedule.hrs) return 'Hours is required';
if (
schedule.days &&
schedule.hrs &&
schedule.hrs > schedule.days * 24
) {
return `Hours cannot exceed ${schedule.days * 24}`;
}
break;
case 'discountValue':
if (schedule.discountValue === null || schedule.discountValue === '')
return 'Discount is required';
if (schedule.discountType === 'P' && schedule.discountValue > 99) {
return 'Percentage discount cannot exceed 99%';
}
if (
schedule.discountType === 'F' &&
schedule.discountValue >= schedule.displayPrice
) {
return 'Fixed discount cannot be greater than or equal to display price';
}
break;
case 'selectedDays':
if (schedule.priority === 'Y' && schedule.selectedDays.length === 0) {
return 'Please select at least one day';
}
break;
}
return null;
};
const validateSchedule = (schedule, index, allSchedules = schedules) => {
const errors = {};
// ✅ Required fields
if (!schedule.days) errors.days = 'Days is required';
if (!schedule.price) errors.price = 'Price is required';
if (!schedule.displayPrice)
errors.displayPrice = 'Display price is required';
if (!schedule.taxId) errors.taxId = 'Tax is required';
if (!schedule.hrs) errors.hrs = 'Hours is required';
if (schedule.discountValue === null || schedule.discountValue === '')
errors.discountValue = 'Discount is required';
if (schedule.priority === 'Y' && schedule.selectedDays.length === 0)
errors.selectedDays = 'Please select at least one day';
// ✅ Display price <= price
if (
schedule.displayPrice &&
schedule.price &&
Number(schedule.displayPrice) > Number(schedule.price)
) {
errors.displayPrice = 'Display price cannot be greater than price';
}
// ✅ Hours ≤ days * 24
if (schedule.days && schedule.hrs && schedule.hrs > schedule.days * 24) {
errors.hrs = `Hours cannot exceed ${schedule.days * 24}`;
}
// ✅ Discount validation
if (schedule.discountType === 'P' && schedule.discountValue > 99) {
errors.discountValue = 'Percentage discount cannot exceed 99%';
}
if (
schedule.discountType === 'F' &&
schedule.discountValue >= schedule.displayPrice
) {
errors.discountValue =
'Fixed discount cannot be greater than or equal to display price';
}
// ✅ Duplicate Check (uses current updated allSchedules)
allSchedules.forEach((other, idx) => {
if (idx === index) return; // skip self
const baseMatch =
Number(other.days) === Number(schedule.days) &&
Number(other.price) === Number(schedule.price) &&
Number(other.displayPrice) === Number(schedule.displayPrice) &&
String(other.taxId) === String(schedule.taxId) &&
Number(other.hrs) === Number(schedule.hrs) &&
String(other.discountType) === String(schedule.discountType) &&
Number(other.discountValue) === Number(schedule.discountValue) &&
String(other.priority) === String(schedule.priority);
if (baseMatch) {
// both are non-priority plans
if (schedule.priority === 'N' && other.priority === 'N') {
errors.duplicate = 'Plan already exists';
}
// both are priority plans -> check selectedDays array match
else if (schedule.priority === 'Y' && other.priority === 'Y') {
const daysMatch =
schedule.selectedDays.length === other.selectedDays.length &&
schedule.selectedDays.every((d) => other.selectedDays.includes(d));
if (daysMatch) {
errors.duplicate = 'Plan already exists (same priority days)';
}
}
}
});
return errors;
};
const addSchedule = () => {
if (!validateMembershipName(membershipName)) {
message.error('Please enter membership name first');
return;
}
const validatedSchedules = schedules.map((schedule, index) => ({
...schedule,
errors: validateSchedule(schedule, index, schedules),
}));
setSchedules(validatedSchedules);
const hasErrors = validatedSchedules.some(
(schedule) => Object.keys(schedule.errors).length > 0
);
if (hasErrors) {
message.error('Please fix all errors before adding a another plan');
return;
}
const membershipId = (editstate && editstate.MembershipId) ? editstate.MembershipId : null;
setSchedules([
...schedules,
{
id: Date.now(),
days: null,
price: null,
displayPrice: null,
taxId: null,
hrs: null,
discountType: 'F',
discountValue: null,
priority: 'N',
selectedDays: [],
remarks: '',
membershipId: membershipId,
uniqueId: null,
planId: null,
activeStatus: null,
errors: {},
},
]);
};
const removeSchedule = (id) => {
if (schedules?.filter(schedule => schedule?.activeStatus !== 'D').length === 1) {
message.warning('At least one plan is required');
return;
}
const scheduleToRemove = schedules.find((sch) => sch.id === id);
const index = schedules.findIndex((sch) => sch.id === id);
if (scheduleToRemove.planId) {
// If planId exists, set activeStatus to 'D'
const newSchedules = [...schedules];
newSchedules[index] = { ...newSchedules[index], activeStatus: 'D' };
setSchedules(newSchedules);
} else {
// If no planId, filter out the record
const newSchedules = schedules.filter((_, i) => i !== index);
setSchedules(newSchedules);
}
};
const handleSubmit = async () => {
if (!validateMembershipName(membershipName)) {
message.error('Please enter membership name');
return;
}
// Validate all schedules and set errors
const validatedSchedules = schedules.map((schedule, index) => ({
...schedule,
errors: validateSchedule(schedule, index, schedules),
}));
setSchedules(validatedSchedules);
const hasErrors = validatedSchedules.some(
(schedule) => Object.keys(schedule.errors).length > 0
);
if (hasErrors) {
message.error('Please fix all errors before submitting');
return;
}
const formData = {
AppId: AppId,
CompId: CompId,
BranchId: BranchId,
MembershipName: membershipName,
MembershipPlanDtl: schedules.map((schedule) => ({
PlanDuration: schedule.days,
DisplayPrice: schedule.displayPrice,
Price: schedule.price,
TaxId: schedule.taxId || null,
FreeSessionHrs: schedule.hrs,
Discount: schedule.discountValue,
DiscountType: schedule.discountType,
Priority: schedule.priority,
Prioritydays: schedule.selectedDays,
Remarks: schedule.remarks || '',
UniqueId: schedule.uniqueId || null,
PlanId: schedule.planId || null,
MembershipId: schedule.membershipId || null,
ActiveStatus: schedule.activeStatus || null,
TaxAmount: calculateTaxAmount(schedule.displayPrice, schedule.taxId),
})),
};
// Add edit-specific fields if in edit mode
let res = {};
if (editstate) {
formData.MembershipId = editstate.MembershipId;
formData.UpdatedBy = UserId;
formData.ActiveStatus = editstate.ActiveStatus;
formData.UniqueId = editstate.UniqueId;
res = await dispatch(putMembership(formData)).unwrap();
} else {
formData.CreatedBy = UserId;
res = await dispatch(postMembership(formData)).unwrap();
}
if (res?.data?.statusCode === 1) {
navigate(`${subDirectory}setting/membership-master/list`, {
state: {
Notify: {
messageType: 'success',
messageData: res?.data?.response,
},
},
});
} else {
setMessageData(res?.data?.response);
setMessageType('error');
}
console.log('Form Data:', formData);
};
const calculateTaxAmount = (price, taxId) => {
const taxRate = taxData.find((tax) => tax.TaxId === taxId)?.TaxPercentage;
return price * (taxRate / 100);
};
const onComplete = useCallback(() => {
setMessageData(null);
setMessageType(null);
}, []);
return (
<section className="membershipContainer">
<Messages
messageType={messageType}
messageData={messageData}
onComplete={onComplete}
/>
<div className="membershipWrapper">
<div className="membership-content">
<h2 className="membershipTitle">
{editstate ? 'Edit Membership' : 'Create Membership'}
</h2>
<div className="membershipFormGroup" style={{ width: '250px' }}>
<label className="membershipFormLabel required">
Membership Name
</label>
<Input
placeholder="Enter membership name"
value={membershipName}
onChange={(e) => {
setMembershipName(e.target.value);
validateMembershipName(e.target.value);
}}
onBlur={(e) => validateMembershipName(e.target.value)}
status={nameError ? 'error' : ''}
className="membershipFormInput"
size="large"
/>
{nameError && (
<div className="membershipErrorText">{nameError}</div>
)}
</div>
{schedules?.filter(schedule => schedule?.activeStatus !== 'D').map((schedule, index) => (
<div key={schedule.id} className="membershipScheduleCard">
<div className="membershipScheduleHeader">
<h3 className="membershipScheduleTitle">Plan {index + 1}</h3>
{schedules?.filter(schedule => schedule?.activeStatus !== 'D').length > 1 && (
<Button
type="text"
danger
icon={<DeleteOutlined />}
onClick={() => removeSchedule(schedule.id)}
className="membershipRemoveBtn"
>
Remove
</Button>
)}
</div>
<div className="membershipFormRow">
<div className="membershipFormGroup">
<label className="membershipFormLabel required">Days</label>
<Input
placeholder="Enter days"
value={schedule.days}
onChange={(value) =>
handleScheduleChange(schedule.id, 'days', value?.target?.value)
}
status={schedule.errors.days ? 'error' : ''}
className="membershipFormInput"
size="large"
style={{ width: '100%' }}
inputMode="decimal"
onInput={(e) => {
const cleanedValue = e.target.value.replace(
/[^0-9.]/g,
''
);
const parts = cleanedValue.split('.');
e.target.value =
parts.length > 2
? `${parts[0]}.${parts.slice(1).join('')}`
: cleanedValue;
}}
/>
{schedule.errors.days && (
<div className="membershipErrorText">
{schedule.errors.days}
</div>
)}
</div>
<div className="membershipFormGroup">
<label className="membershipFormLabel required">Price</label>
<Input
placeholder="Enter price"
value={schedule.price}
onChange={(value) =>
handleScheduleChange(schedule.id, 'price', value?.target?.value)
}
// min={0}
// prefix="₹"
status={schedule.errors.price ? 'error' : ''}
className="membershipFormInput"
size="large"
style={{ width: '100%' }}
inputMode="decimal"
onInput={(e) => {
const cleanedValue = e.target.value.replace(
/[^0-9.]/g,
''
);
const parts = cleanedValue.split('.');
e.target.value =
parts.length > 2
? `${parts[0]}.${parts.slice(1).join('')}`
: cleanedValue;
}}
/>
{schedule.errors.price && (
<div className="membershipErrorText">
{schedule.errors.price}
</div>
)}
</div>
<div className="membershipFormGroup">
<label className="membershipFormLabel required">
Display Price
</label>
<Input
placeholder="Enter display price"
value={schedule.displayPrice}
onChange={(value) =>
handleScheduleChange(
schedule.id,
'displayPrice',
value?.target?.value
)
}
// min={0}
// prefix="₹"
status={schedule.errors.displayPrice ? 'error' : ''}
className="membershipFormInput"
size="large"
style={{ width: '100%' }}
inputMode="decimal"
onInput={(e) => {
const cleanedValue = e.target.value.replace(
/[^0-9.]/g,
''
);
const parts = cleanedValue.split('.');
e.target.value =
parts.length > 2
? `${parts[0]}.${parts.slice(1).join('')}`
: cleanedValue;
}}
/>
{schedule.errors.displayPrice && (
<div className="membershipErrorText">
{schedule.errors.displayPrice}
</div>
)}
</div>
</div>
<div className="membershipFormRow1">
<div className="membershipFormGroup TaxinputMemeber">
<label className="membershipFormLabel required">
Tax (%)
</label>
<Select
placeholder="Select Tax"
value={schedule.taxId}
onChange={(value) =>
handleScheduleChange(schedule.id, 'taxId', value)
}
className="membershipFormInput"
size="large"
>
{taxData?.map((item) => (
<Option
key={item.TaxId}
value={item.TaxId}
>{`${item.TaxIdName} - ${item?.TaxPercentage}%`}</Option>
))}
</Select>
{schedule.errors.taxId && (
<div className="membershipErrorText">
{schedule.errors.taxId}
</div>
)}
</div>
<div className="membershipFormGroup hourinputMemeber">
<label className="membershipFormLabel required">Hours</label>
<Input
placeholder="Enter hours"
value={schedule.hrs}
onChange={(value) =>
handleScheduleChange(schedule.id, 'hrs', value?.target?.value)
}
// min={0}
status={schedule.errors.hrs ? 'error' : ''}
className="membershipFormInput"
size="large"
style={{ width: '100%' }}
inputMode="decimal"
onInput={(e) => {
const cleanedValue = e.target.value.replace(
/[^0-9.]/g,
''
);
const parts = cleanedValue.split('.');
e.target.value =
parts.length > 2
? `${parts[0]}.${parts.slice(1).join('')}`
: cleanedValue;
}}
/>
{schedule.errors.hrs && (
<div className="membershipErrorText">
{schedule.errors.hrs}
</div>
)}
</div>
<div className="membershipFormGroup">
<label className="membershipFormLabel required">
Discount Type
</label>
<Select
placeholder="Select discount type"
value={schedule.discountType}
onChange={(value) =>
handleScheduleChange(schedule.id, 'discountType', value)
}
className="membershipFormInput"
size="large"
>
<Option value="F">Fixed</Option>
<Option value="P">Percentage</Option>
</Select>
</div>
<div className="membershipFormGroup">
<label className="membershipFormLabel required">
Discount Value
</label>
<InputNumber
placeholder="Enter discount"
value={schedule.discountValue}
onChange={(value) =>
handleScheduleChange(schedule.id, 'discountValue', value)
}
min={0}
prefix={schedule.discountType === 'F' ? '₹' : ''}
suffix={schedule.discountType === 'P' ? '%' : ''}
max={schedule.discountType === 'P' ? 100 : undefined}
status={schedule.errors.discountValue ? 'error' : ''}
className="membershipFormInput"
size="large"
style={{ width: '100%' }}
/>
{schedule.errors.discountValue && (
<div className="membershipErrorText">
{schedule.errors.discountValue}
</div>
)}
</div>
</div>
<div className="membershipFormGroup">
<label className="membershipFormLabel required">Priority</label>
<Radio.Group
value={schedule.priority}
onChange={(e) =>
handleScheduleChange(schedule.id, 'priority', e.target.value)
}
size="large"
>
<Radio value="Y">Yes</Radio>
<Radio value="N">No</Radio>
</Radio.Group>
</div>
{schedule.priority === 'Y' && (
<div className="membershipFormGroup">
<label className="membershipFormLabel required">
Select Days
</label>
<Select
mode="multiple"
placeholder="Select days"
value={schedule.selectedDays}
onChange={(value) =>
handleScheduleChange(schedule.id, 'selectedDays', value)
}
status={schedule.errors.selectedDays ? 'error' : ''}
className="membershipFormInput"
size="large"
>
{DAYS_OF_WEEK.map((day) => (
<Option key={day.value} value={day.value}>
{day.label}
</Option>
))}
</Select>
{schedule.errors.selectedDays && (
<div className="membershipErrorText">
{schedule.errors.selectedDays}
</div>
)}
</div>
)}
<div className="membershipFormGroup">
<label className="membershipFormLabel">Remarks</label>
<Input.TextArea
placeholder="Enter remarks"
value={schedule.remarks}
onChange={(e) =>
handleScheduleChange(schedule.id, 'remarks', e.target.value)
}
rows={3} // Adjust the number of rows as needed
className="membershipFormInputTextArea"
size="large"
/>
</div>
{schedule.errors.duplicate && (
<div className="membershipDuplicateError">
<span className="membershipErrorText">
{schedule.errors.duplicate}
</span>
</div>
)}
</div>
))}
<div className="membershipFormActions">
<Button
type="dashed"
icon={<PlusOutlined />}
onClick={addSchedule}
className="membershipAddBtn"
size="large"
>
Add More Plan
</Button>
<Button
type="primary"
onClick={handleSubmit}
className="membershipSubmitBtn"
size="large"
>
{formType === 'add' ? `Create Membership` : `Update Membership`}
</Button>
</div>
</div>
</div>
</section>
);
};
export default MembershipForm;